diff --git a/docs/middleware-performance-improvements.md b/docs/middleware-performance-improvements.md index 1178b725a6..82fbce7a8d 100644 --- a/docs/middleware-performance-improvements.md +++ b/docs/middleware-performance-improvements.md @@ -87,39 +87,28 @@ Add a per-request memoization slot to the `runMiddlewares` ctx: ### Trade-off -Adds one `new Map()` per request (~100ns) for the savings of two `getRegion` -calls. Net win is small per request, but the cache slot is a foundation any -future middleware can use for free. Worth doing as infrastructure even if -region-specific savings are modest. +**Not a real perf win in isolation.** Adds ~100-200ns of cache infrastructure +per request (Map allocation + lookups) to save ~200-500ns from one redundant +`getRegion` call — roughly a wash. Only worth doing as foundation if/when we +need to share other parsed-once data between middlewares (parsed UA, parsed +cookies, etc.). For getRegion alone, skip it. **Estimated change size**: ~30 lines across 4 files. Low risk — purely additive. --- -## ⏳ Low priority — static-file extension regex +## ✅ Static-file extension regex (cleanliness) [`shouldRunRegionMiddleware`](../src/server/middleware/region-middleware-utils.ts) -checks for static file extensions via: +now uses a precompiled regex instead of `staticFileExtensions.some(...)`. -```ts -if (pathname.includes('.') && staticFileExtensions.some((ext) => pathname.endsWith(ext))) { - return false; -} -``` - -Could be replaced with a precompiled regex: - -```ts -const STATIC_FILE_EXT_RE = /\.(ico|png|jpg|jpeg|gif|svg|webp|css|js|woff2?|ttf|eot|xml|json)$/i; -// in shouldRunRegionMiddleware: -if (STATIC_FILE_EXT_RE.test(pathname)) return false; -``` - -The redundant `pathname.includes('.')` guard goes away — the regex covers it. - -**Savings**: ~150ns per request that doesn't match a static file. Negligible -at request scale; do it for cleanliness if/when somebody is in the area. +**Was not actually a perf win**: the original `pathname.includes('.')` guard +short-circuited in ~10ns for paths without a dot (most page traffic), which +is faster than a regex test. The regex is roughly equal or slightly slower +on the typical hot path. Shipped purely for code clarity — the redundant +`includes('.')` guard goes away and the array of strings becomes one +self-documenting pattern. --- @@ -142,26 +131,30 @@ here so we don't re-discover it. --- -## 🤔 Deferred — `redirectsMiddleware` over-eager session fetch +## ✅ `redirectsMiddleware` removed — moved to `next.config.mjs` -[`redirectsMiddleware`](../src/server/middleware/redirects.middleware.ts) -declares `useSession: true` because the `/user/@me` redirect needs the -current user. But the runner pre-fetches the next-auth token before the -handler runs, so non-`@me` `/user/` paths pay the JWT-verify cost -(~50-200µs of crypto) for nothing. +Previously declared `useSession: true` because the `/user/@me` redirect +needed the current user, which forced the runner to JWT-verify (~50-200µs) +on every `/user/*` request — including all the ones that didn't actually +need it. -Two ways to skip the wasted token fetch: +**Resolved by**: -1. **Split into two `addRedirect` calls**: one matcher for `/user/civitai` - (no session needed), one for `/user/@me/:path*` (session needed). Requires - `addRedirect` to support per-redirect `useSession`. Decent refactor of the - helper. -2. **Move `useSession` from middleware-level to handler-level**, lazily - fetching the token only when the handler actually accesses `user`. Larger - change to the `Middleware` type and runner. +1. Removing `/user/@me` (no internal links referenced it). +2. Changing `/user/civitai → /404` to `/user/civitai → /user/CivitaiOfficial`. +3. Moving the remaining redirect from middleware into `next.config.mjs`'s + `redirects()` array. Next.js processes config-level redirects **before** + middleware runs, so `/user/civitai` never hits the middleware chain. -**Recommendation**: skip both unless `/user/` traffic shows up as a -hot path. Listed here for completeness. +The middleware file is deleted. The runner no longer iterates a redirect +entry for any `/user/*` request. + +**Savings**: + +- ~50-200µs per `/user/` request (no JWT verify) +- One fewer middleware iteration in the chain on every request +- `/user/civitai` itself: framework-level redirect, no JS execution per + request (faster than middleware-driven redirect) --- diff --git a/next.config.mjs b/next.config.mjs index d88f6fe192..bb4c3a1d51 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -311,6 +311,15 @@ export default defineNextConfig( destination: '/games/knights-of-new-order', permanent: true, }, + { + // Reserved-name redirect: the legacy 'civitai' user account moved to + // 'CivitaiOfficial'. Handled here (not in middleware) so it runs at + // framework/edge level before any middleware, with no JS execution + // per request. + source: '/user/civitai', + destination: '/user/CivitaiOfficial', + permanent: true, + }, ]; }, output: 'standalone', diff --git a/src/server/middleware/index.ts b/src/server/middleware/index.ts index dcef2492cf..528abce8dd 100644 --- a/src/server/middleware/index.ts +++ b/src/server/middleware/index.ts @@ -7,7 +7,6 @@ import { apiCacheMiddleware } from '~/server/middleware/api-cache.middleware'; import { apiRegionBlockMiddleware } from '~/server/middleware/api-region-block.middleware'; import { botDetectionMiddleware } from '~/server/middleware/bot-detection.middleware'; import type { Middleware } from '~/server/middleware/middleware-utils'; -import { redirectsMiddleware } from '~/server/middleware/redirects.middleware'; import { regionBlockMiddleware } from '~/server/middleware/region-block.middleware'; import { regionRestrictionMiddleware } from '~/server/middleware/region-restriction.middleware'; import { previewAuthMiddleware } from '~/server/middleware/preview-auth.middleware'; @@ -25,7 +24,6 @@ const middlewares: Middleware[] = [ apiRegionBlockMiddleware, routeGuardsMiddleware, apiCacheMiddleware, - redirectsMiddleware, botDetectionMiddleware, ]; diff --git a/src/server/middleware/redirects.middleware.ts b/src/server/middleware/redirects.middleware.ts deleted file mode 100644 index 5da2b2e9eb..0000000000 --- a/src/server/middleware/redirects.middleware.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { pathToRegexp } from 'path-to-regexp'; -import type { Middleware } from '~/server/middleware/middleware-utils'; -import { createMiddleware } from '~/server/middleware/middleware-utils'; - -const redirects: Redirect[] = []; - -const userNamePathRegexp = pathToRegexp('/user/:username/:path*'); -addRedirect({ - matcher: ['/user/:path*'], - handler: async ({ redirect, request, user }) => { - const [, username] = userNamePathRegexp.exec(request.nextUrl.pathname) ?? []; - if (username === 'civitai') return redirect('/404'); - if (username === '@me') - if (user) return redirect(request.nextUrl.href.replace('/@me', '/' + user.username)); - else return redirect('/login?returnUrl=' + request.nextUrl.pathname); - }, -}); - -//#region Logic -type Redirect = { - matcher: string[]; - isMatch: (pathname: string) => boolean; - handler: Middleware['handler']; -}; -function addRedirect(redirect: Omit) { - const regexps = redirect.matcher.map((m) => pathToRegexp(m)); - const isMatch = (pathname: string) => regexps.some((r) => r.test(pathname)); - - return redirects.push({ - ...redirect, - isMatch, - }); -} -export const redirectsMiddleware = createMiddleware({ - matcher: redirects.flatMap((redirect) => redirect.matcher), - useSession: true, - handler: async (ctx) => { - const { pathname } = ctx.request.nextUrl; - - for (const redirect of redirects) { - if (!redirect.isMatch(pathname)) continue; - const response = redirect.handler(ctx); - if (response) return response; - } - }, -}); -//#endregion diff --git a/src/server/middleware/region-middleware-utils.ts b/src/server/middleware/region-middleware-utils.ts index 4acaa80b24..630c7184fa 100644 --- a/src/server/middleware/region-middleware-utils.ts +++ b/src/server/middleware/region-middleware-utils.ts @@ -7,23 +7,9 @@ export const excludedPaths = ['/api', '/_next', '/fonts', '/sounds']; export const excludedFiles = ['favicon.ico', 'robots.txt', 'site.webmanifest']; -export const staticFileExtensions = [ - '.ico', - '.png', - '.jpg', - '.jpeg', - '.gif', - '.svg', - '.webp', - '.css', - '.js', - '.woff', - '.woff2', - '.ttf', - '.eot', - '.xml', - '.json', -]; +// Static asset file extensions — paths ending in these are skipped by the +// region middlewares. Compiled once at module init. +const STATIC_FILE_EXT_RE = /\.(ico|png|jpg|jpeg|gif|svg|webp|css|js|woff2?|ttf|eot|xml|json)$/i; // Common matcher pattern for region middlewares export const regionMiddlewareMatcher = [ @@ -53,9 +39,7 @@ export function shouldRunRegionMiddleware(pathname: string, excludeRegionBlocked } // Don't run on any static files (including static images) - if (pathname.includes('.') && staticFileExtensions.some((ext) => pathname.endsWith(ext))) { - return false; - } + if (STATIC_FILE_EXT_RE.test(pathname)) return false; return true; }