Fondu

Project structure

View as Markdown

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.