documentation menu

Frameworks

Which folder your framework builds to, and whether it needs --spa — the whole answer in one table.

read as markdown

Pages serves static files, so the only two things you need to know about your framework are which folder it builds to and whether that folder is a single-page app. Everything else is the same command.

FrameworkBuildOutput--spa?Live
Vite (React/Vue/Svelte)npm run builddist/yes, with client-side routingdemo
Next.jsnpm run buildout/nodemo
Astronpm run builddist/nodemo
Nuxtnpx nuxt generate.output/public/nodemo
SvelteKitnpm run buildbuild/only with an SPA fallbackdemo
Create React Appnpm run buildbuild/yesdemo
Hugohugopublic/nodemo
Eleventynpx @11ty/eleventy_site/nodemo

Every demo is deployed by the template repository behind it, on every push, using the same GitHub Action you would use. Press Use this template and you have a repo that does the same:

template-vitetemplate-nextjstemplate-astrotemplate-nuxt
template-sveltekittemplate-create-react-apptemplate-hugotemplate-eleventy

Starting a new site rather than adding a deploy to one? Templates has whole sites — a blog, a docs site, a landing page with a working waitlist and a portfolio — set up the same way.

Built with Lovable, Bolt, v0, ChatGPT or Claude? Start at AI tools instead.

Not listed? If it writes HTML, CSS and JS to a folder, it works — point one9x pages release at that folder. The rule of thumb for --spa: if the build produces one index.html and the router runs in the browser, you need it. If it produces a real HTML file per route, you don’t.

The --spa decision, in one line

--spa serves /index.html with a 200 for any path that doesn’t match a file.

Without it, a client-rendered app 404s the moment someone hard-refreshes a deep route or follows a shared link — the app is fine, the router never got to run. With it on a site that has real per-route HTML files, a genuine typo’d URL returns your homepage with a 200 instead of a 404, which is worse for both readers and search engines.

Details and error-page mapping are in SPA mode & error pages.

Things that apply to every framework

Dotfiles are skipped. Anything beginning with a dot — .git, .env, .DS_Store — stays on your machine. .well-known is the single exception, because assetlinks.json and apple-app-site-association only work at that exact URL.

This matters for Nuxt, whose output directory is .output/public/ — the dot is on a parent of what you deploy. You point the release at the inner folder (one9x pages release ./.output/public --site mysite), so nothing is dropped.

--exclude globs are single-segment. They use Go’s path.Match, so ** is not supported and a pattern using it matches nothing rather than erroring. Match on the file name ('*.map') or exclude the directory (admin).

--spa and --index are the same slot. Pass one, not both.

Pages serves paths, not routes. A request for /about is served /about/index.html — a build that wrote about.html instead will 404 on every internal link while the homepage works fine. Most generators do the right thing; Next.js and SvelteKit need a setting (trailingSlash), and both framework pages say so.

Build in CI, not on the deploying machine, if you can. GitHub Actions gets you a preview URL per pull request and a publish on merge, which is the point at which the framework question stops mattering — the workflow names the folder once and nobody thinks about it again.