[]previous and next page

Keep secrets out of the browser

Three mechanisms keep server code out of client bundles.

For agentsUpdated

Three mechanisms keep server code out of client bundles. Together they make the question "can this reach the browser?" a build error instead of a code review.

The server function transform (above) removes handlers, schemas and middleware, and every import that only they used. src/server/db.server.ts is imported by notes.ts, but only the handlers use it, so a route that imports getNote pulls neither the database client nor its connection string into the browser bundle.

Import protection runs on every import in project code. In the browser build these imports are errors:

  • the server-only entries @shadow-js/dusk/server, @shadow-js/dusk/node, @shadow-js/dusk/static and @shadow-js/dusk/testing;
  • Node built-ins (node:fs, fs, crypto, ...);
  • files named *.server.ts (also .tsx, .js, .jsx, .mts, .mjs) and the globs of importProtection.server in the options of dusk().

In the server build, files named *.client.* and the globs of importProtection.client are errors. The importProtection.ignore globs exempt files, and importProtection: false turns the check off. A violation fails the build (and the dev server's request) with [IMPORT_PROTECTED] and the chain of imports from the entry, one module per line with the position of each import. Imports in node_modules are not checked, and type-only imports are erased before the check. Because the check sees the import after the server function transform has run, a server module that only handlers import passes.

Code that runs in both places, such as a loader, imports HttpError, redirect and notFound from @shadow-js/dusk, which holds nothing server-only; @shadow-js/dusk/server is for server code.

The environment check covers import.meta.env. The browser receives only the variables whose names start with DUSK_PUBLIC_ (plus Vite's own MODE, BASE_URL, PROD, DEV and SSR); a prefix you add through Vite's envPrefix must have three or more characters, and * or a short one fails with [ENV_PREFIX_UNSAFE]. A read of any other name in a module of the client build is an error, [ENV_NOT_PUBLIC], because it would silently be undefined in production. The check reads the module as written, before the server functions are replaced, so a read inside a handler counts when the file is part of the client graph. Read server secrets in a .server module, or through event.env and defineEnv (see Dusk: security).