# Frameworks

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

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.

| Framework | Build | Output | `--spa`? | Live |
| --- | --- | --- | --- | --- |
| [Vite](/docs/frameworks/vite) (React/Vue/Svelte) | `npm run build` | `dist/` | yes, with client-side routing | [demo](https://vite.one9x.app/) |
| [Next.js](/docs/frameworks/nextjs) | `npm run build` | `out/` | no | [demo](https://nextjs.one9x.app/) |
| [Astro](/docs/frameworks/astro) | `npm run build` | `dist/` | no | [demo](https://astro.one9x.app/) |
| [Nuxt](/docs/frameworks/nuxt) | `npx nuxt generate` | `.output/public/` | no | [demo](https://nuxt.one9x.app/) |
| [SvelteKit](/docs/frameworks/sveltekit) | `npm run build` | `build/` | only with an SPA fallback | [demo](https://sveltekit.one9x.app/) |
| [Create React App](/docs/frameworks/create-react-app) | `npm run build` | `build/` | yes | [demo](https://create-react-app.one9x.app/) |
| [Hugo](/docs/frameworks/hugo) | `hugo` | `public/` | no | [demo](https://hugo.one9x.app/) |
| [Eleventy](/docs/frameworks/eleventy) | `npx @11ty/eleventy` | `_site/` | no | [demo](https://eleventy.one9x.app/) |

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-vite](https://github.com/one9x/template-vite) | [template-nextjs](https://github.com/one9x/template-nextjs) | [template-astro](https://github.com/one9x/template-astro) | [template-nuxt](https://github.com/one9x/template-nuxt) |
| [template-sveltekit](https://github.com/one9x/template-sveltekit) | [template-create-react-app](https://github.com/one9x/template-create-react-app) | [template-hugo](https://github.com/one9x/template-hugo) | [template-eleventy](https://github.com/one9x/template-eleventy) |

Starting a new site rather than adding a deploy to one? [Templates](/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](/docs/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](/docs/spa-and-errors).

## 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](/docs/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.