Static Generation
Davaux supports static site generation (SSG) out of the box. The davaux static command compiles your application and pre-renders every page to a plain HTML file — no Node.js required at runtime.
Running the static build
davaux static
This command:
- Compiles your TypeScript source and bundles client islands
- Starts an in-process server
- Crawls every known route and renders it to HTML
- Writes the output to the
out/directory
The entire process is self-contained — you do not need to run davaux build first.
Once the build completes, run davaux preview to serve the output locally and verify everything looks right before deploying:
davaux static && davaux preview
Output structure
The URL tree maps directly to the file tree:
| Route | Output file |
|---|---|
/ | out/index.html |
/about | out/about/index.html |
/blog/hello-world | out/blog/hello-world/index.html |
/api/... | (API routes are skipped in SSG) |
Any static assets from your public/ directory are copied as-is. The compiled client island bundles land in out/_davaux/.
SSG config options
Several static generation options can be set permanently in davaux.config.ts under the ssg key:
// davaux.config.ts
import { defineConfig } from 'davaux/config'
export default defineConfig({
ssg: {
outDir: 'dist/static',
trailingSlash: 'never',
basePath: '/my-project',
notFound: true,
sitemap: { baseUrl: 'https://example.com' },
},
})
outDir
Override the default out/ output directory. The --out CLI flag still takes priority when both are set.
davaux static # writes to outDir from config, or out/ if not set
davaux static --out /tmp/preview # CLI flag wins
trailingSlash
Controls how route paths map to output files:
| Setting | /about renders to |
|---|---|
'always' (default) | out/about/index.html — served as /about/ |
'never' | out/about.html — served as /about |
Choose 'never' when your host doesn't rewrite /about → /about/index.html automatically.
basePath
Path prefix for deployments to a subdirectory — for example, a project deployed to GitHub Pages at https://user.github.io/my-project/:
ssg: { basePath: '/my-project' }
Davaux prepends the basePath to every injected /_davaux/* script and stylesheet URL in the generated HTML. Links within your own pages are your responsibility — use the same prefix in your <a href> values.
notFound — 404.html for static hosts
Static hosts (Netlify, GitHub Pages, Cloudflare Pages) serve a 404.html from the site root for unmatched paths. When your app has a _error.tsx, Davaux generates this file automatically by running the error page through its normal rendering pipeline:
ssg: { notFound: true } // explicit (default when _error.tsx exists)
ssg: { notFound: false } // suppress even when _error.tsx exists
The generated 404.html gets the same layout and styles as your other error pages.
sitemap
Auto-generate a sitemap.xml from all successfully rendered routes:
ssg: { sitemap: { baseUrl: 'https://example.com' } }
The sitemap respects trailingSlash and basePath. The root route / always appears as https://example.com/. Submit the sitemap URL to Google Search Console after deploying.
Changing the output directory via CLI
The --out flag is a one-off override — use ssg.outDir in config for a permanent setting:
davaux static --out dist
davaux static --out /var/www/html
Dynamic routes and getStaticPaths
Static routes (no [brackets]) are rendered automatically. Dynamic routes need you to declare which paths to render by exporting a getStaticPaths function:
// src/routes/blog/[slug].page.tsx
import { definePage, defineStaticPaths } from 'davaux'
export const getStaticPaths = defineStaticPaths(async () => {
const posts = await db.posts.findAll()
return posts.map((post) => ({ params: { slug: post.slug } }))
})
export default definePage(async (ctx) => {
const post = await db.posts.findBySlug(ctx.params.slug)
ctx.head.title = post.title
return (
<article>
<h1>{post.title}</h1>
<div>{post.htmlContent}</div>
</article>
)
})
defineStaticPaths returns an array of { params } objects. Each entry causes Davaux to render one HTML file during the static build.
If a dynamic route is missing getStaticPaths, Davaux prints a warning with a code hint and skips that route — the build continues instead of failing. If an individual page throws during rendering it is also warned and skipped, so one broken page does not abort the whole output.
Per-route rendering mode
By default every page route is included in the static build. Set prerender = false to opt a route out:
import { definePage } from 'davaux'
// This route is skipped during `davaux static`
export const prerender = false
export default definePage(async (ctx) => {
// auth-gated, personalised, or otherwise dynamic content
})
A common pattern is to mix both modes in one app:
// src/routes/index.page.tsx — rendered to out/index.html at build time
export default definePage(() => <LandingPage />)
// src/routes/blog/[slug].page.tsx — rendered to out/blog/*/index.html at build time
export const getStaticPaths = defineStaticPaths(async () => { /* ... */ })
export default definePage(async (ctx) => { /* ... */ })
// src/routes/dashboard.page.tsx — skipped by davaux static, served dynamically
export const prerender = false
export default definePage(async (ctx) => {
const user = ctx.state.session.get('userId')
if (!user) redirect('/login')
return <Dashboard userId={user} />
})
Deploy out/ to a CDN and run davaux start for the server-rendered routes. An nginx reverse proxy can serve both from one domain:
server {
# Try the static file first; fall back to the Node.js process
location / {
root /var/www/out;
try_files $uri $uri/index.html @node;
}
location @node {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Static routes are served by nginx at CDN speed with no Node.js involved. Server routes hit the Node process only when needed.
Middleware during static generation
The full middleware chain runs during SSG exactly as it does for live requests. App-level middleware (src/middleware.ts) and scoped _middleware.ts files all execute. This means authentication, headers, and other middleware side-effects apply to generated output — useful for setting cache headers or canonical URLs.
Limitations
Because SSG pre-renders at build time with a synthetic GET request, some request-specific data is unavailable:
ctx.cookies— no browser cookies during build, so cookie-gated content will see an unauthenticated requestctx.req.headers— headers from a real browser (Accept-Language, User-Agent, etc.) are not presentctx.req.method— alwaysGETduring generation
Design SSG pages to work with no session state. For personalized content, either fetch it client-side from a createResource in an island, or mark the route with export const prerender = false to skip SSG entirely and serve it dynamically.
Deploying the output
The out/ directory is a self-contained static site. Deploy it to any static host:
Netlify:
netlify deploy --dir out --prod
Vercel:
{
"outputDirectory": "out"
}
Cloudflare Pages:
wrangler pages deploy out
Plain nginx:
server {
root /var/www/out;
index index.html;
try_files $uri $uri/ $uri/index.html =404;
}
Islands in static output
Islands are fully supported in static output. Each island's initial HTML is pre-rendered into the page (no blank flash), and the hydration bundle is included as a <script> tag pointing to _davaux/client.js. Interactive islands work identically to SSR mode — the client picks up the pre-rendered HTML and hydrates it in place.