fix: serve built frontend in production node server#83
Conversation
The Hono dev migration (PR #81) broke `npm start` — the ASSETS stub always returned 404, so the built SPA was never served. Replace the stub with a real file server that reads dist/public/ and falls back to index.html for SPA routing when NODE_ENV=production. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@coderabbitai review Please evaluate:
|
📝 WalkthroughWalkthroughThe Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
chittyfinance | 322712c | Apr 08 2026, 04:42 AM |
Code ReviewGood fix for the broken Critical: Path Traversal Vulnerabilityconst filePath = resolve(publicDir, url.pathname.replace(/^\//, ''));
const filePath = resolve(publicDir, url.pathname.replace(/^\//, ''));
if (!filePath.startsWith(publicDir + '/') && filePath !== publicDir) {
return new Response('Not found', { status: 404 });
}Performance: Synchronous I/O on the Event Loop
import { readFile, stat } from 'node:fs/promises';
// in the fetch handler:
try {
const stats = await stat(filePath);
if (stats.isFile()) {
const body = await readFile(filePath);
// ...
}
} catch {}The top-level Missing Cache HeadersNo const isAsset = url.pathname.startsWith('/assets/');
const headers = {
'content-type': mime,
'cache-control': isAsset ? 'public, max-age=31536000, immutable' : 'no-cache',
};Minor: Silent
|
There was a problem hiding this comment.
Pull request overview
This PR restores npm start behavior after the Hono dev migration by making the Node dev/production entrypoint able to serve the built SPA from disk in production, while keeping dev behavior unchanged (Vite serves the frontend).
Changes:
- Replaces the
ASSETS404 stub with a production file server fordist/public/(SPAindex.htmlfallback). - Adds MIME type detection for served static assets.
- Keeps dev-mode behavior as a 404 stub for
ASSETS.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@server/dev.ts`:
- Around line 13-14: The imports readFileSync, statSync, and resolve violate
edge-runtime rules; remove those Node core imports and replace their usage with
Web-compatible APIs: use new URL('./relative/path', import.meta.url) to resolve
paths instead of resolve, use fetch(...) to load file contents instead of
readFileSync, and derive metadata (size, mtime) from the Response or a dev HTTP
metadata endpoint instead of statSync; update any code referencing
readFileSync/statSync/resolve accordingly (search for readFileSync, statSync,
resolve in this module and replace with fetch + URL-based resolution and a small
metadata adapter).
- Around line 55-64: The file-serving code builds filePath from url.pathname
without checking for path traversal; update the handler that computes filePath
to validate the resolved path stays inside publicDir by resolving publicDir and
filePath (using realpath/resolve) and comparing (e.g., with path.relative or
startsWith) so if the resolved filePath is outside publicDir you return a
404/403 instead of reading the file; keep the existing statSync/readFileSync
logic but only run it after this containment check (refer to filePath, publicDir
and the async request handler).
- Line 46: The resolved publicDir is incorrect — change how publicDir is
computed so it points to the built assets at dist/public in production; update
the declaration that currently uses resolve(import.meta.dirname ?? '.',
'public') (and the publicDir variable used by the index read and file serving)
to resolve(import.meta.dirname ?? '.', 'dist', 'public') when isProduction is
true so production asset reads/serving use the correct directory.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
- Validate resolved file paths stay within publicDir (prevents traversal) - Only return SPA fallback HTML for GET requests (POST/PUT → 404) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: address PR #83 review — path traversal guard + SPA method check - Validate resolved file paths stay within publicDir (prevents traversal) - Only return SPA fallback HTML for GET requests (POST/PUT → 404) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
npm start— the ASSETS stub always returned 404, so the built SPA was never serveddist/public/and falls back toindex.htmlfor SPA routing whenNODE_ENV=productionTest plan
npm run build && PORT=5015 NODE_ENV=production node dist/dev.jscurl localhost:5015/→ 200, HTMLcurl localhost:5015/assets/*.css→ 200, correct MIMEcurl localhost:5015/dashboard→ 200, SPA fallbackcurl localhost:5015/health→ 200, JSONnpm run dev) still returns 404 for/(Vite serves frontend)🤖 Generated with Claude Code
Summary by CodeRabbit