Static rendering
Aplos can pre-render pages to static HTML at build time. Add a directive at the top of any page and it becomes static — instant first paint, full SEO, deployable to any static host.
Opt a page in
// src/pages/about.tsx "use static"; export default function About() { return <h1>About</h1>; }
When you run bun run build --static, Aplos emits public/dist/about.html containing the fully rendered HTML. Crawlers see the content. The browser displays it before any JavaScript runs.
Build with static rendering
bun run build --static
Or, in package.json:
{ "scripts": { "build": "aplos build --static" } }
Output:
Pre-rendering 3 route(s)...
✓ / → public/dist/index.html
✓ /about → public/dist/about.html
✓ /contact → public/dist/contact.html
Mix static and dynamic
Pages without "use static" are still served as a SPA. You can mix freely:
src/pages/
index.tsx # "use static" → /
about.tsx # "use static" → /about
dashboard.tsx # SPA → /dashboard (client-rendered)
This is the SPA-first, static-on-opt-in model: keep dynamic logic where you need it, pre-render the parts that don't change per user.
Pre-render dynamic routes
To pre-render a route like /blog/:slug, list the values to expand at build time via the paths option in aplos.config.js:
// aplos.config.js export default { routes: [ { path: '/blog/:slug', paths: ['hello-world', 'second-post'], }, { path: '/products/:id', paths: async () => { const products = await fetchProducts(); return products.map((p) => p.id); }, }, ], };
At build time, each combination is expanded into its own static HTML file:
public/dist/blog/hello-world.html
public/dist/blog/second-post.html
public/dist/products/42.html
public/dist/products/43.html
Per-route metadata
Each static page can export a meta object to set its <title>, description,
canonical URL, Open Graph and Twitter Card tags. The values are extracted at
build time and injected directly into the pre-rendered HTML.
// src/pages/about.tsx "use static"; export const meta = { title: "About — Acme", description: "Learn about Acme.", og: { type: "website", image: "/og.png" }, }; export default function About() { return <h1>About</h1>; }
See the full reference in Per-route metadata.
When to use static rendering
Good fits:
- Marketing pages, blogs, documentation
- Product catalogs with known SKUs at build time
- Anything indexable by search engines
Not a good fit:
- Pages that depend on the logged-in user
- Pages whose content changes per request
For those, leave them as SPA pages — they will be client-rendered as before.
How it works
When you run aplos build --static, Aplos:
- Builds a server-side rendering bundle targeting Node (Rspack with
target: 'node'). - Collects the routes marked as static (via
"use static"or matching apathsconfig). - Executes the SSR bundle in Node and renders each route to HTML.
- Writes the resulting HTML next to your client-side bundles.
Dynamic routes that are not in the static set fall back to the SPA shell exactly as before — nothing changes for them.