Project structure
The files
my-app/
├─ app/
│ ├─ server.ts # the server: export default createApp()
│ ├─ app.tsx # createInertiaApp(): the browser and the server render
│ ├─ routes/ # server code only: the file path is the URL
│ │ ├─ _middleware.ts # runs for its directory and below
│ │ ├─ index.ts # /
│ │ ├─ users/[id].ts # /users/:id
│ │ └─ api/health.ts # /api/health
│ ├─ pages/ # React pages, by name: 'users/show' is pages/users/show.tsx
│ │ └─ error.tsx # the error page (404, 403, 500…)
│ └─ fondu.gen.d.ts # the generated types (commit it)
├─ package.json # fondu, hono, @inertiajs/react, react, react-dom
└─ vite.config.ts # plugins: [fondu({ target: 'node' })]vite.config.ts
fondu() is the Vite plugin. It takes the target, node, bun or cloudflare:
import fondu from 'fondu/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [fondu({ target: 'node' })],
})On Cloudflare, the Cloudflare plugin comes first and runs the Worker in workerd, in development too:
plugins: [cloudflare({ viteEnvironment: { name: 'ssr' } }), fondu({ target: 'cloudflare' })]app/server.ts
The server: createApp() returns a Hono app made of app/routes/, with CSRF protection, security headers (a Content
Security Policy included), one URL per page (/users/ redirects to /users) and error pages built in.
import { createApp } from 'fondu'
export default createApp()Its options: document (the HTML around pages), errorPage, trailingSlash: 'append', csrf and secureHeaders
(Hono's options, or false). The API lists them.
app/app.tsx
The browser's entry, and the server render's: Inertia's createInertiaApp(). Fondu's Vite plugin finds the pages in
./pages and wraps the same call for the server.
import { createInertiaApp } from '@inertiajs/react'
import Layout from './layout'
createInertiaApp({
pages: './pages',
title: (title) => (title ? `${title} · My app` : 'My app'),
layout: () => Layout,
})Server code
app/routes/, app/server.ts and files named *.server.ts are server code: browser code must not import them, and
the build stops if it does. Server and client code explains where each file runs.
package.json maps #/* to ./app/*, so import { db } from '#/lib/db.server.ts' works from anywhere in app/.
app/fondu.gen.d.ts
The dev server and the build write the types of your pages and routes here: they make render() and href() typed.
Commit it, so the types are there before the first build (in CI, say); don't edit it.
Editor snippets
A new app has .vscode/fondu.code-snippets: type /route, /api, /loader, /action, /actions, /validated,
/get … /delete or /middleware in a route file, /page, /layout or /form in a page. Edit them freely.