Fondu

Server and client code

View as Markdown

Every file in app/ runs on the server, in the browser, or in both. Where a file runs decides what it can do, and whether it ends up in the JavaScript your visitors download.

Where each file runs

FilesRunsIn the browser's bundle
app/routes/, app/server.ts, *.server.tson the server onlynever
app/app.tsx, app/pages/, their componentson the server (the first render), then in the browseryes
anything else they import (a schema, a type)wherever it is importedif a page imports it

The browser's bundle is everything app/app.tsx and the pages import, directly or not. Each page is a chunk of its own, loaded when it is visited.

Server code

app/routes/, app/server.ts and files named *.server.ts are server code: they read the database, the secrets, the session. If a page or a component imports one, even through another file, the build stops and names the import:

fondu: /app/lib/db.server.ts is server code (routes/, server.ts, *.server.ts) and cannot be imported by browser code

Name a module *.server.ts when it must never reach the browser, and import it from routes and other server files.

Code for both

Pages and their components run twice: on the server for the first render, then in the browser. So they must not touch window or document while rendering (do it in an effect, or inside Inertia's <WhenMounted>), and they can't reach the server's resources: what they need comes as props.

A module with no secrets, such as a validation schema, can be shared: a route validates with it, a page checks the same rules before sending.

Props are public

Props are the only way data goes from a route to a page, and they are written into the HTML and the JSON of the page: anyone who can see the page can read them. Send what the page shows, not whole records: { name: user.name }, not a user row with its password hash.

Environment variables and secrets

  • On Cloudflare: bindings and secrets in c.env, in handlers.
  • On Node and Bun: process.env, in server code.
  • import.meta.env.VITE_* is written into the browser's bundle by Vite: only for values that are public anyway.