Skip to content

feat: image cache warmup, query params, and docs#110

Merged
rsbh merged 5 commits into
mainfrom
feat/image-cache-warmup
May 27, 2026
Merged

feat: image cache warmup, query params, and docs#110
rsbh merged 5 commits into
mainfrom
feat/image-cache-warmup

Conversation

@rsbh
Copy link
Copy Markdown
Member

@rsbh rsbh commented May 27, 2026

Summary

  • Remove 500 entry cache limit
  • Extract optimizeImage function for reuse
  • Add warmupImageCache() using getPages() + getPageImages(), triggered from readiness probe
  • Support ?w=640&q=90 query params on image URLs in markdown
  • Add image optimization documentation

Changes

  • src/server/api/image.ts — remove cache limit, extract optimizeImage, add warmupImageCache
  • src/server/api/ready.ts — trigger warmup alongside search index
  • src/lib/remark-resolve-images.ts — parse w/q query params from image URLs
  • docs/content/docs/image-optimization.mdx — full documentation

Test plan

  • Server starts, readiness probe triggers warmup
  • Cache populated with all content images
  • Query params parsed and applied
  • Build and 147 tests pass

- Remove 500 entry cache limit and eviction logic
- Extract optimizeImage function for reuse
- Add image-warmup Nitro plugin that pre-caches all content images
  as webp at default width after first response
- Export STORAGE_KEY and cacheKey for plugin access

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chronicle Error Error May 28, 2026 2:08am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Review Change Stack

Warning

Review limit reached

@rsbh, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 51 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 774537ec-2a7e-4c4a-83fb-b4a205a385e5

📥 Commits

Reviewing files that changed from the base of the PR and between 7863020 and 6485f05.

📒 Files selected for processing (5)
  • docs/content/docs/image-optimization.mdx
  • packages/chronicle/src/lib/remark-resolve-images.ts
  • packages/chronicle/src/server/api/image.test.ts
  • packages/chronicle/src/server/api/image.ts
  • packages/chronicle/src/server/api/ready.ts
📝 Walkthrough

Walkthrough

The PR extracts image optimization logic from the request handler into a reusable public function and introduces a server startup plugin that pre-warms the image cache by scanning the content directory and optimizing undiscovered raster images with default parameters.

Changes

Image Cache Optimization and Warmup

Layer / File(s) Summary
Image optimization extraction and handler refactor
packages/chronicle/src/server/api/image.ts
STORAGE_KEY is now exported. The inline sharp optimization pipeline from the request handler is extracted into a public optimizeImage(filePath, w, q, format) function that reads, resizes, and encodes images. The handler is updated to call optimizeImage() and cache the result; cache eviction logic is removed.
Image cache warmup plugin
packages/chronicle/src/server/plugins/image-warmup.ts
A new Nitro plugin adds a findImages(dir) helper that recursively discovers raster images in a directory. The plugin hooks afterResponse to scan the content directory once, compute WebP cache keys for each image with default width and quality, and pre-optimize uncached images via the extracted optimizeImage() function, storing results in Nitro storage. Optimization failures are silently skipped and a success count is logged.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately reflects the main changes: image cache warmup feature, query parameter support, and documentation additions.
Description check ✅ Passed The description directly relates to the changeset, detailing cache limit removal, function extraction, warmup implementation, and documentation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/image-cache-warmup

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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/server/api/image.ts`:
- Around line 123-125: The cache key generation for optimized images (variable
key, created via cacheKey) lacks a source fingerprint, so replacing the original
file can leave stale optimized bytes in the fs-backed image-cache; update the
logic around the work closure that calls optimizeImage and storage.setItemRaw to
include a source version signal (e.g., file mtime or a content hash) in the
cache key or store metadata with the cached entry and validate staleness before
returning cached data. Concretely, compute a fingerprint from
fs.stat(filePath).mtimeMs or a short hash of the source file, incorporate that
fingerprint into cacheKey(url, w, q, format, fingerprint) when writing via
storage.setItemRaw, and ensure the read path that looks up cached entries
verifies the same fingerprint (or checks stored metadata) and falls back to
re-optimizing when mismatched.

In `@packages/chronicle/src/server/plugins/image-warmup.ts`:
- Around line 16-20: The code currently allows recursion when
entry.isSymbolicLink() is true, which can follow symlinks outside the content
tree; update the logic in findImages so you do not recurse into symlinked
directories: use fs.lstat or the Dirent methods to detect symlinks
(entry.isSymbolicLink()) and skip them when deciding to recurse, and only
recurse when the target is a real directory (e.g., keep the stat check and call
images.push(...await findImages(fullPath)) only when entry.isDirectory() is true
and entry.isSymbolicLink() is false or when lstat confirms it's not a symlink).
- Around line 35-52: The warmup currently hard-codes format = 'webp' so warmed
cache misses AVIF-capable requests; change the warmup to produce the same
formats the image handler would negotiate by either calling/shared negotiation
logic from the image handler (if there's an exported function) or iterating the
preferred formats (e.g. 'avif' then 'webp') and generating cache entries for
each format: update image-warmup.ts to replace the single const format = 'webp'
with a loop over formats (or call the handler's negotiate function), then call
cacheKey(url, w, q, format), storage.getItemRaw/setItemRaw and
optimizeImage(filePath, w, q, format) for each format to ensure AVIF cache
entries are created too.
🪄 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: 2a78c8f3-a383-4893-b24a-31546ae3ef6e

📥 Commits

Reviewing files that changed from the base of the PR and between 4d8965a and 7863020.

📒 Files selected for processing (2)
  • packages/chronicle/src/server/api/image.ts
  • packages/chronicle/src/server/plugins/image-warmup.ts

Comment thread packages/chronicle/src/server/api/image.ts
Comment thread packages/chronicle/src/server/plugins/image-warmup.ts Outdated
Comment thread packages/chronicle/src/server/plugins/image-warmup.ts Outdated
Delete separate image-warmup plugin. Warmup now uses getPages() +
getPageImages() from source.ts (images already collected by remark
plugin). Triggered from ready probe alongside search index.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Authors can now specify w and q on image URLs:
  ![alt](photo.png?w=640&q=90)

Params are parsed from the URL, stripped before file resolution,
and passed through to the optimization endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers format negotiation, query params (w/q), allowed values,
API endpoint, caching, and warmup behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rsbh rsbh changed the title feat: remove cache limit and warm up image cache on start feat: image cache warmup, query params, and docs May 27, 2026
Cache key now includes source file mtime — replacing an image
invalidates stale cache entries. Added biome-ignore for useStorage
in warmupImageCache.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
export async function warmupImageCache() {
const { getPages, getPageImages } = await import('@/lib/source');
// biome-ignore lint/correctness/useHookAtTopLevel: useStorage is a Nitro DI accessor, not a React hook
const storage = useStorage(STORAGE_KEY);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would this work with other toolkits apart from nitro?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, it needs nitro. but we get abstration over different run time and different stoage options
https://nitro.build/docs/storage

@rsbh rsbh merged commit 7c9b4c2 into main May 27, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants