chore: split browser and server TypeScript environments in apps/web#1091
Merged
igboyes merged 3 commits intoJul 2, 2026
Conversation
The single apps/web tsconfig pulled @types/node into global scope for the whole program, so browser code type-checked against Node's ambient types instead of the DOM. Introduce two type-check projects: - tsconfig.server.json emits declarations for src/server (and the server entry files) with Node types. - tsconfig.app.json checks browser code with a DOM lib and no Node types, resolving @server/* to the emitted declarations so server source no longer enters the browser program. start.ts is reachable from the client via routeTree.gen.ts, so it must be browser-safe: switch its relative ./server/* imports to the @server alias (resolved to declarations in the browser project) and replace node:crypto with the Web Crypto API. The upload @ts-expect-error stays: superagent's .attach types are Node-flavored and its deps force @types/node globals into the browser program via `reference types="node"`. This split does not remove that; it goes away when superagent is dropped for server functions.
Client-reachable files bypass the browser/server tsconfig split's declaration remap when they use relative ./server/* imports, dragging @types/node globals back into the browser program. Document the @server/* alias rule alongside the two-project type-check setup.
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
start.ts,documentHeadersMiddlewareruns on the server but now relies onbtoaandcrypto.getRandomValues, which are browser APIs; consider usingBuffer.from(...).toString('base64')or a shared utility that works in both Node and the browser to avoid runtime failures in SSR. - The new nonce generation logic is inlined inside
documentHeadersMiddleware; extracting it into a small helper (e.g.generateNonce()) would make it easier to reuse and to swap out if the underlying random/encoding mechanism needs to change for different runtimes.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `start.ts`, `documentHeadersMiddleware` runs on the server but now relies on `btoa` and `crypto.getRandomValues`, which are browser APIs; consider using `Buffer.from(...).toString('base64')` or a shared utility that works in both Node and the browser to avoid runtime failures in SSR.
- The new nonce generation logic is inlined inside `documentHeadersMiddleware`; extracting it into a small helper (e.g. `generateNonce()`) would make it easier to reuse and to swap out if the underlying random/encoding mechanism needs to change for different runtimes.
## Individual Comments
### Comment 1
<location path="apps/web/src/start.ts" line_range="84-85" />
<code_context>
const html = await response.text();
- const nonce = randomBytes(16).toString("base64");
+ const nonce = btoa(
+ String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))),
+ );
const body = html.replace(/<script(?=[\s>])/g, `<script nonce="${nonce}"`);
</code_context>
<issue_to_address>
**issue (bug_risk):** Using `btoa` and `crypto.getRandomValues` here may break in non-browser server runtimes.
Because this middleware is declared with `.server(...)`, it will likely run in a server/edge runtime, not a browser. In Node, `globalThis.crypto.getRandomValues` is available, but `btoa` is not, so this will throw a `ReferenceError` at runtime. If this runs on Node, please either keep `randomBytes(16).toString("base64")` or use a Node-safe base64 approach (e.g. `Buffer.from(bytes).toString("base64")`). If you truly depend on an environment that provides both `crypto.getRandomValues` and `btoa`, ensure the build cannot be executed in plain Node where `btoa` is missing.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Document why the nonce uses Web Crypto + btoa rather than node:crypto/Buffer: start.ts is reachable from the browser program and must type-check without Node types. Both globals exist in the Node runtime, so the server middleware is safe.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
apps/webtype-checking into two projects:tsconfig.server.json(Node types) emits declarations forsrc/server, andtsconfig.app.json(DOM lib, no Node types) checks browser code and resolves@server/*to those declarations — so server source no longer enters the browser program.pnpm typecheckruns both.start.tsbrowser-safe (it's pulled into the client viarouteTree.gen.ts): switch its relative./server/*imports to the@serveralias and replacenode:cryptowith the Web Crypto API.@ts-expect-errorstays for now — it stems from superagent's Node-flavored.attachtypes and goes away when superagent is dropped for server functions, not from this split. Documented the@server/*alias rule inAGENTS.md.