Skip to content

chore: split browser and server TypeScript environments in apps/web#1091

Merged
igboyes merged 3 commits into
mainfrom
igboyes/vir-2571-split-browser-and-server-typescript-environments-in-appsweb
Jul 2, 2026
Merged

chore: split browser and server TypeScript environments in apps/web#1091
igboyes merged 3 commits into
mainfrom
igboyes/vir-2571-split-browser-and-server-typescript-environments-in-appsweb

Conversation

@igboyes

@igboyes igboyes commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Split apps/web type-checking into two projects: tsconfig.server.json (Node types) emits declarations for src/server, and tsconfig.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 typecheck runs both.
  • Make start.ts browser-safe (it's pulled into the client via routeTree.gen.ts): switch its relative ./server/* imports to the @server alias and replace node:crypto with the Web Crypto API.
  • This is the precondition for a genuinely Node-free browser program. The upload @ts-expect-error stays for now — it stems from superagent's Node-flavored .attach types and goes away when superagent is dropped for server functions, not from this split. Documented the @server/* alias rule in AGENTS.md.

igboyes added 2 commits July 2, 2026 14:00
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.
@linear-code

linear-code Bot commented Jul 2, 2026

Copy link
Copy Markdown

VIR-2571

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread apps/web/src/start.ts Outdated
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.
@igboyes igboyes merged commit e755c1c into main Jul 2, 2026
10 of 11 checks passed
@igboyes igboyes deleted the igboyes/vir-2571-split-browser-and-server-typescript-environments-in-appsweb branch July 2, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant