Deployment
Build and deploy your Fumapress app.
Overview
fumapress build writes the app to dist/:
dist/public: static assets and prerendered pages.dist/server: the server bundle.
Two settings control the result: the render mode decides how much of the app is prerendered, and the deployment adapter packages the output for your hosting platform.
Render Mode
Set mode in your press config:
import { defineConfig } from "fumapress";
export default defineConfig({
// ...
mode: "static",
});default: pages are prerendered, API routes such as the search endpoint still run on a server.static: only static files are emitted, deploydist/publicto any static host or CDN.dynamic: pages render on request, for content sources that change after the build.
See Render Mode for choosing one. A route's own render config takes priority over the global mode, see Routing.
Static Mode
With mode: "static", every route is prerendered into dist/public:
- An HTML file per page, and a
404.html. - The output of enabled plugins, like
sitemap.xmland Open Graph images. - The search index at
/api/search, downloaded by the search dialog to run queries in the browser.
Plugins that require a server fail the build with an error:
Self-hosted image optimization is skipped automatically, and deployment adapters skip their server: no Vercel or Netlify function, no Cloudflare Worker.
Platforms
Each platform is served by a deployment adapter, detected from environment variables at build time: Vercel, Netlify, and Cloudflare set them on their build machines, other environments fall back to waku/adapters/node. To build for a platform from elsewhere (e.g. your own CI), set the adapter option:
import { defineConfig } from "vite";
import press from "fumapress/vite";
export default defineConfig({
plugins: [
press({
adapter: "waku/adapters/cloudflare",
}),
],
});The adapter also selects the image optimization provider on Vercel, Cloudflare, and Node.js.
Node.js
The default target. Build, then start the server:
npm run build
npm run startfumapress start runs the server entry emitted by the Node.js adapter, which serves both dist/public and dynamic routes. It reads the PORT and HOST environment variables, or pass --port/--host. You can also run the entry directly without the CLI:
PORT=8080 node dist/serve-node.jsFor image optimization on Node.js, install sharp as a dependency, the recommended preset picks it up automatically.
Vercel
No configuration needed: import the repository, and the build produces a Build Output API structure with static files and a serverless function for dynamic routes. Image optimization uses Vercel Image Optimization.
Netlify
The build generates:
netlify.toml, skipped when your project has one:dist/publicas the publish directory, a Netlify Function for dynamic routes, and Pretty URLs disabled so pages are served without a trailing slash.dist/public/_headers, skipped when your project has apublic/_headers: caches hashed assets for a year.
Commit the generated netlify.toml, or keep those settings in your own.
Cloudflare
The app deploys to Cloudflare Workers with static assets: prerendered pages and assets are uploaded as files, a Worker serves the remaining routes. With mode: "static" there is no Worker at all. Workers Builds is detected automatically; to build elsewhere, set the adapter as shown above.
The build generates:
| File | Purpose |
|---|---|
wrangler.jsonc | Wrangler config named after package.json, skipped when your project has one. Static mode adds not_found_handling: "404-page", so unknown URLs get the nearest 404.html. |
dist/public/_headers | Caches hashed assets under /assets/ for a year, skipped when your project has a public/_headers. Other responses keep Cloudflare's default of revalidating on every request. |
With your own Wrangler config, the build reuses its name and compatibility settings and points Wrangler at the built Worker; in static mode, set assets.directory to ./dist/public yourself. Deploy right after building:
npx wrangler deployImage optimization uses Cloudflare Image Transformations, enable it on your zone. Workers Builds does not expose the site URL, set site.baseUrl.
Trailing Slashes
The generated Wrangler config sets html_handling: "drop-trailing-slash", so /docs/ redirects to /docs. Keep it with your own Wrangler config.
Static Hosting
For GitHub Pages, CDNs, or any other static file host: set mode: "static", build, and upload dist/public. The emitted 404.html is picked up by hosts that support it.
When the site is served from a sub-path (e.g. https://user.github.io/repo/), set basePath:
import { defineConfig } from "vite";
import press from "fumapress/vite";
export default defineConfig({
plugins: [
press({
basePath: "/repo/",
}),
],
});Include the sub-path in site.baseUrl (e.g. https://user.github.io/repo), absolute URLs like og:image, sitemap, and RSS are joined to it.
Environment Variables
The CLI loads .env and .env.local (plus mode-specific variants like .env.production) before running, variables already set in the environment take priority. Read them with process.env in press.config.tsx and server code, no prefix required:
const client = createClient({
token: process.env.CMS_TOKEN,
});Client-side code follows the usual Vite convention: only variables prefixed with VITE_ are exposed through import.meta.env.
Files that must stay readable only on the server, such as key files, belong in the private directory (configurable with the privateDir plugin option).
Also set site.baseUrl to your production URL, metadata, feeds, and sitemap entries resolve against it.
Last updated on
