Build & Deploy
Mage Pages generates a static site that can be deployed anywhere.
Building for Production
Create a build script:
// build.ts
import { pages } from "@mage/app/pages";
const { build } = pages({
siteMetadata: {
baseUrl: "https://example.com",
},
});
await build();
Run the build:
deno run -A build.ts
This generates a dist/ directory with your static site.
Build Output
The build process creates:
dist/
├── index.html # /
├── about/
│ └── index.html # /about
├── docs/
│ ├── index.html # /docs
│ └── getting-started/
│ └── index.html # /docs/getting-started
├── __bundles/
│ ├── index-a1b2c3d4.js # Page bundles (hashed)
│ └── about-e5f6g7h8.js
├── __public/
│ └── styles-i9j0k1l2.css # Static assets (hashed)
├── _not-found.html # 404 page
├── sitemap.xml # Auto-generated sitemap
└── robots.txt # Auto-generated robots.txt
Asset Hashing
All assets are content-hashed for cache busting:
/public/styles.css→/__public/styles-a1b2c3d4.css- Bundle filenames include content hashes
URLs in HTML are automatically rewritten to use hashed paths.
Sitemap Generation
A sitemap.xml is generated with all pages:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
</url>
<url>
<loc>https://example.com/about</loc>
</url>
</urlset>
Requires siteMetadata.baseUrl to be set.
Robots.txt
A basic robots.txt is generated:
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
Serving Static Files
For local testing or self-hosting, use the static server:
// serve.ts
import { MageApp } from "@mage/app";
import { pages } from "@mage/app/pages";
const app = new MageApp();
const { registerStaticServer } = pages();
registerStaticServer(app, { rootDir: "./dist" });
Deno.serve(app.handler);
Run it:
deno run -A serve.ts
The static server handles:
- Serving pre-built HTML files
- Serving hashed assets with cache headers
- 404 fallback to
_not-found.html
Development vs Production
| Feature | Development | Production |
|---|---|---|
| Rendering | On-demand | Pre-built |
| Bundles | Inline, unminified | Separate files, minified |
| Assets | Direct paths | Content-hashed paths |
| Hot reload | Yes | No |
| Sourcemaps | Inline | None |
Deployment Targets
Deno Deploy
Create main.ts:
import { MageApp } from "@mage/app";
import { pages } from "@mage/app/pages";
const app = new MageApp();
const { registerStaticServer } = pages();
registerStaticServer(app, { rootDir: "./dist" });
Deno.serve(app.handler);
Build locally and deploy:
deno run -A build.ts
deployctl deploy --project=your-project main.ts
Or build on deploy with a GitHub action.
Cloudflare Pages
Build your site:
deno run -A build.ts
Configure Cloudflare Pages:
- Build command:
deno run -A build.ts - Build output directory:
dist
GitHub Pages
Use a GitHub Action:
name: Deploy to GitHub Pages
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Build
run: deno run -A build.ts
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
Docker
Build and serve with the static server:
// serve.ts
import { MageApp } from "@mage/app";
import { pages } from "@mage/app/pages";
const app = new MageApp();
const { registerStaticServer } = pages();
registerStaticServer(app, { rootDir: "./dist" });
const port = parseInt(Deno.env.get("PORT") ?? "8000");
Deno.serve({ hostname: "0.0.0.0", port }, app.handler);
Create a Dockerfile:
FROM denoland/deno:2.5.6
WORKDIR /app
# Cache dependencies
COPY deno.json deno.lock ./
RUN deno install
# Copy source and build
COPY . .
RUN deno run -A build.ts
# Cache the serve module
RUN deno cache serve.ts
EXPOSE 8000
CMD ["deno", "run", "--allow-net", "--allow-read", "--allow-env", "serve.ts"]
Build and run:
docker build -t my-site .
docker run -p 8000:8000 my-site
Static File Hosting
Any static file host works. Just upload the dist/ directory:
- AWS S3 + CloudFront
- Google Cloud Storage
- Azure Blob Storage
- nginx
- Apache
Configure your host to serve _not-found.html for 404 responses.
Build Configuration
await build({
rootDir: "./", // Source directory (default: "./")
outDir: "./dist", // Output directory (default: "{rootDir}/dist")
basePath: "/", // URL base path (default: "/")
markdownOptions: {
shikiTheme: "github-dark",
wrapperClassName: "prose",
},
});
For sites hosted at a subpath (e.g., https://example.com/docs/):
await build({
basePath: "/docs",
});
Next Steps
- Configuration - All configuration options