Repository navigation
refactor: rate limiter and add route preloading support - #879
Conversation
…d content-write budgets under a safety cap Reads (GET/HEAD) and mutations now spend independent budgets, so route preloading can no longer use up the allowance a visitor needs to submit a form. A global safety cap counts every request, including OPTIONS preflights. Legacy top-level rateLimiter options map onto the safety cap with their old defaults, so existing installs keep their protection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
…load The app and starter router wait 250ms of hover before preloading, so a pointer sweeping across a list no longer runs a loader for every link. page(), index() and layout() accept preload: boolean, inherited through layouts, which maps to TanStack's route-level preload so a hover skips that route's loader. Guards still run on every navigation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
…che-backed loaders Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
Blog and Forum loaders called the fetcher directly, so every hover preload and the click after it re-sent their API requests. They now warm per-feature queryOptions with deliberate stale times, so repeat preloads and hover-then- click share one request. Answers that differ by viewer (hidden records, private responses, a member's submissions) are never reused across users, and forum writes invalidate the lists, threads and stats they change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
…derData Blog and Forum loaders read through TanStack Query and also return the data as loaderData, so the server-rendered HTML carried it twice. Queries marked with LOADER_DATA_QUERY_META are now left out of the SSR dehydration that createVitNodeQueryClient configures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
…META Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
…s after preflights A signed-in member's read, write and content-write budgets are now keyed by their user ID once globalMiddleware has verified the session, so people behind one office IP no longer share them and a member cannot refresh them by switching networks. Guests and unverified cookies stay keyed by IP, and the safety cap and sign-in budget always are. rateLimiter.perUser: false restores IP-only keys. A preflight is still counted against the safety cap but never refused: the real request that follows gets the 429, which runs after cors() and so carries CORS headers. Retry-After and X-RateLimit-Category are exposed to cross-origin pages, merged into any custom cors.exposeHeaders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
…le preflight 429s Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
…nt navigation Plugin pages rendered internal navigation as plain <a href>, so the router never preloaded or client-navigated it and the Forum's query cache never paid off. HrefLink (@vitnode/core/tanstack/link) turns an href string with query and hash into a router Link, so plugins keep using strings without importing the host route tree; useHrefNavigate does the same from code. AdminLink now builds on it, which also fixes admin hrefs with a query. Creating a topic invalidates the forum queries and navigates client-side instead of reloading the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
React Doctor found 1 new issue in 1 file · 1 warning · score 66 / 100 (Needs work) · 0 fixed · vs 1 warning
Reviewed by React Doctor for commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d80e4cc799
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…larity refactor(api): ✏️ clean up comments in rate limiter and auth cookie files refactor(tanstack): ✏️ simplify HrefLink and preload logic in plugin routes refactor(tests): ✏️ remove unnecessary comments in test files for clarity
| : ip; | ||
|
|
||
| try { | ||
| await limiters[category].limiter.consume(key); |
There was a problem hiding this comment.
React Doctor · react-doctor/async-await-in-loop (warning)
This for…of loop waits before starting the next iteration. If iterations perform independent asynchronous work, consider bounded concurrency; await syntax alone does not establish a speedup.
Fix → Consider concurrent calls only for independent asynchronous work. Shared queues or synchronous work may not benefit. Preserve resource limits, transaction ordering, and failure/cancellation semantics; observe all callback promises.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dce5ddd7aa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| carriesSession(c) | ||
| ? categories.filter(category => !PER_USER_CATEGORIES.has(category)) | ||
| : categories, |
There was a problem hiding this comment.
Do not trust cookie presence before applying IP limits
When perUser is enabled, any nonempty session cookie causes the read/write/content-write IP limits to be skipped here, even though the cookie is not verified until globalMiddleware calls SessionModel.getUser(). An attacker can therefore attach a forged cookie and force up to the 1,000-request safety budget through session hashing/device and database lookups before identified eventually falls back to the IP limit, bypassing the intended 300-read or 60-write protection around that expensive work. Apply a provisional IP category limit until a valid user identity has actually been established, then transfer/refund that consumption for authenticated users.
Useful? React with 👍 / 👎.
Description
What?
This PR makes two major changes to VitNode:
Rate Limiter Refactoring: Splits the monolithic rate limiter into separate budgets for safety, read, write, auth, and content-write operations. Introduces a new
RateLimiterConfiginterface andrate-limiter-config.tsmodule to manage configuration. The middleware now:X-RateLimit-Categoryheader to identify which budget was exhaustedRoute Preloading: Adds support for controlling whether routes preload on link hover via a
preloadboolean option. Includes:HrefLinkcomponent anduseHrefNavigatehook for internal navigationrateLimitPathTemplate()function to anonymize paths in logspreloadoption with inheritance from parent layoutsWhy?
Rate Limiter: The previous single-budget approach couldn't distinguish between different request types. The new multi-budget system provides:
Preloading: Link preloads can trigger expensive API calls when visitors sweep across lists. The new
preloadoption lets routes opt out, reducing unnecessary API traffic and rate limit consumption during casual browsing.Test Plan
HrefLinkcomponent navigation behaviorhttps://claude.ai/code/session_01F1sfMPmQoAgrC8t5g5Kk3n