fix: serve static content files and resolve image paths#46
Conversation
Nitro wraps plain return values in NodeResponse, which Bun rejects in dev mode (nitrojs/nitro#4228). Return standard Response/Response.json directly to bypass this. Also read readingTime from eager frontmatter glob instead of loading full MDX module in API route. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Catch-all route serves non-md/mdx files (images, PDFs, etc.) from the .content directory. Blocks .md/.mdx and path traversal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remark plugin rewrites image src (both markdown  and JSX <img>) to absolute /_content/ URLs based on the MD file's location within the content directory. External URLs left untouched. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remark plugin resolves image src (markdown, HTML, JSX, HAST) relative to the MD file's location. Disable fumadocs remarkImage to prevent it from converting src to JS imports before our plugin runs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace string array visit with type-checked node.type guard to fix TS overload error. Add @types/unist to chronicle devDeps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR refactors server route handlers to return explicit ChangesImage Resolution & Static Content
Reading Time Metadata Injection
Response Handler Standardization
Sequence DiagramsequenceDiagram
participant Browser
participant MarkdownProcessor as Markdown<br/>Processor
participant RemarkPlugin as Remark<br/>Image Plugin
participant ContentRoute as _content<br/>Route
participant FileSystem as File<br/>System
Browser->>MarkdownProcessor: Load markdown with images
MarkdownProcessor->>RemarkPlugin: Parse and traverse AST
RemarkPlugin->>RemarkPlugin: Detect image nodes<br/>(img, html, JSX)
RemarkPlugin->>RemarkPlugin: Resolve relative paths to<br/>/_content/...
RemarkPlugin->>MarkdownProcessor: Return modified AST
MarkdownProcessor->>Browser: Render with updated image URLs
Browser->>ContentRoute: GET /_content/images/example.png
ContentRoute->>FileSystem: Resolve safe path from<br/>CHRONICLE_CONTENT_DIR
FileSystem->>ContentRoute: File contents
ContentRoute->>Browser: Response with MIME type<br/>+ Cache-Control header
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/chronicle/src/lib/source.ts (1)
239-251: ⚡ Quick winConsider extracting the
_readingTimenormalization into a shared helper.The clamped rounding formula
minutes != null ? Math.max(1, Math.round(minutes)) : undefinedis duplicated verbatim inbuildFiles(Line 47) and here. If the "minimum 1 minute" policy changes, it needs updating in both places.♻️ Proposed refactor
+function normalizeReadingTime(minutes: number | undefined): number | undefined { + return minutes != null ? Math.max(1, Math.round(minutes)) : undefined; +} // in buildFiles(): -const _readingTime = rt?.minutes != null ? Math.max(1, Math.round(rt.minutes)) : undefined; +const _readingTime = normalizeReadingTime(rt?.minutes); // in loadPageModule(): const minutes = mod.readingTime?.minutes; -return { default: mod.default ?? null, toc: mod.toc ?? [], _readingTime: minutes != null ? Math.max(1, Math.round(minutes)) : undefined }; +return { default: mod.default ?? null, toc: mod.toc ?? [], _readingTime: normalizeReadingTime(minutes) };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/chronicle/src/lib/source.ts` around lines 239 - 251, The reading-time normalization logic duplicated between loadPageModule and buildFiles should be extracted to a single helper (e.g., normalizeReadingTime or clampReadingMinutes) to avoid drift; create a small function that accepts minutes?: number and returns minutes != null ? Math.max(1, Math.round(minutes)) : undefined, export or place it where both loadPageModule and buildFiles can import it, then replace the inline expression in loadPageModule (the _readingTime calculation) and the corresponding calculation in buildFiles to call that helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/chronicle/src/lib/remark-resolve-images.ts`:
- Around line 22-26: Normalize path separators on the `file.path` string before
checking for '/content/': replace backslashes with forward slashes (e.g.,
normalize `filePath = filePath.replace(/\\+/g, '/')`) so `contentIdx =
filePath.lastIndexOf('/content/')` works on Windows; then continue to compute
`relative` and `dir` from that normalized `filePath` (these symbols: `filePath`,
`contentIdx`, `relative`, `dir` in remark-resolve-images.ts).
- Around line 9-14: The code currently treats protocol-relative URLs like
"//cdn.example.com/a.png" as root-relative and rewrites them; update the check
in the image-resolving logic (the block using src, path.posix.normalize/join in
remark-resolve-images.ts) to early-return protocol-relative URLs unchanged by
adding a condition such as if (src.startsWith('//')) return src before the
existing if (src.startsWith('/')) branch so that "//..." is not rewritten to
"/_content//...".
In `@packages/chronicle/src/server/routes/_content/`[...path].ts:
- Around line 24-25: Call to safePath(pathname, contentDir) can throw on
malformed percent-encoding and currently becomes a 500; wrap the safePath
invocation in a try/catch and treat any thrown error (or a falsy return) as a
controlled 404 by throwing new HTTPError({ status: 404, message: 'Not Found' }).
Specifically, update the logic around safePath/filePath so that errors from
safePath and a null/undefined filePath both result in the same HTTPError 404
response.
---
Nitpick comments:
In `@packages/chronicle/src/lib/source.ts`:
- Around line 239-251: The reading-time normalization logic duplicated between
loadPageModule and buildFiles should be extracted to a single helper (e.g.,
normalizeReadingTime or clampReadingMinutes) to avoid drift; create a small
function that accepts minutes?: number and returns minutes != null ? Math.max(1,
Math.round(minutes)) : undefined, export or place it where both loadPageModule
and buildFiles can import it, then replace the inline expression in
loadPageModule (the _readingTime calculation) and the corresponding calculation
in buildFiles to call that helper.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 56109651-e503-4c0d-9d62-792bc8f5036f
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (16)
packages/chronicle/package.jsonpackages/chronicle/src/lib/remark-resolve-images.tspackages/chronicle/src/lib/source.tspackages/chronicle/src/server/api/apis-proxy.tspackages/chronicle/src/server/api/health.tspackages/chronicle/src/server/api/page.tspackages/chronicle/src/server/api/search.tspackages/chronicle/src/server/api/specs.tspackages/chronicle/src/server/routes/[...slug].md.tspackages/chronicle/src/server/routes/[version]/llms.txt.tspackages/chronicle/src/server/routes/_content/[...path].tspackages/chronicle/src/server/routes/llms.txt.tspackages/chronicle/src/server/routes/og.tsxpackages/chronicle/src/server/routes/robots.txt.tspackages/chronicle/src/server/routes/sitemap.xml.tspackages/chronicle/src/server/vite-config.ts
Skip protocol-relative URLs (//cdn.example.com) in image resolver. Catch malformed percent-encoding in _content route safePath call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Responsefrom all Nitro server handlers to fix Bun dev modeNodeResponseerror (nitrojs/nitro#4228)/_content/catch-all route to serve static files (images, PDFs, etc.) from content directoriesremarkResolveImagesplugin to resolve imagesrcpaths relative to the MD/MDX file locationreadingTimefrom eager frontmatter glob instead of loading full MDX module in API routeremarkImageto prevent it from converting image src to JS importsTest plan
<img src="images/vpn/vpn_07.png">in.mdxfiles resolves to/_content/terranova/images/vpn/vpn_07.png/_content/route serves images with correct content typesyntax also resolves correctlyhttps://...) in image src are untouched🤖 Generated with Claude Code