|
| 1 | +# Adapter request chain |
| 2 | + |
| 3 | +Adapters receive information about output pathnames, but the Adapters API/specification does not |
| 4 | +fully document how to stitch those outputs together to implement routing that matches Next.js |
| 5 | +behavior. |
| 6 | + |
| 7 | +This document describes the Next.js request chain logic for adapter implementers. It gathers |
| 8 | +behavior observed in the Next.js runtime and Vercel adapters and documents required routing logic |
| 9 | +that currently needs to be reverse-engineered. The document describes what logic must be applied, |
| 10 | +but does not prescribe implementation details — it is up to implementers to decide where and how to |
| 11 | +apply this logic in their specific adapter architecture. |
| 12 | + |
| 13 | +**Note:** This document intentionally does not cover ISR/prerender grouping and revalidation or PPR |
| 14 | +(Partial Prerendering) handling, as those belong to the details of prerender output handling and not |
| 15 | +to routing or output matching logic. |
| 16 | + |
| 17 | +Legend: |
| 18 | + |
| 19 | +- `[onBuildComplete routes]`: information supplied to the adapter via its `onBuildComplete` |
| 20 | + callback. This information is site/application-specific. |
| 21 | +- `[generic rules]`: Generic, non-site-specific routing behaviors that are not provided via the |
| 22 | + adapter's `onBuildComplete` callback and are not currently documented in the specification. Many |
| 23 | + of these rules are conditional — for example, they may only apply when the Pages router or App |
| 24 | + router is used, when middleware is enabled, when i18n is configured. |
| 25 | + |
| 26 | +Routing rules: |
| 27 | + |
| 28 | +1. Incoming request phase |
| 29 | + |
| 30 | + All incoming requests go through this phase first, before any output matching is attempted. |
| 31 | + |
| 32 | + Steps: |
| 33 | + 1. `[onBuildComplete routes]` Priority redirects (trailing slash): if the request pathname's |
| 34 | + trailing slash doesn't match configuration, terminate with a redirect that adds or strips the |
| 35 | + trailing slash. |
| 36 | + 2. `[generic rules]` If middleware/proxy is enabled and using the Pages router: |
| 37 | + 1. If the request path matches `<basepath>/_next/data/<build_id>/<path>.json`, add the |
| 38 | + `x-next-data: 1` request header and continue. |
| 39 | + 2. If the request path matches `<basepath>/_next/data/<build_id>/<path>.json`, rewrite the |
| 40 | + request path to `<basepath>/<path>` (index special case) and continue. |
| 41 | + 3. `[generic rules]` If i18n is enabled: |
| 42 | + 1. If locale domains are configured: |
| 43 | + 1. Rewrite the pathname to ensure the correct locale for the matched domain is prefixed to |
| 44 | + the pathname (index special case) and continue. |
| 45 | + 2. If locale detection is enabled and the request targets the index route (including |
| 46 | + locale-prefixed ones), detect the locale based on the `NEXT_LOCALE` cookie or |
| 47 | + `Accept-Language` header and redirect to the locale-specific domain. TODO: avoid |
| 48 | + redirect loops — only redirect when the current pathname is not already the expected |
| 49 | + pathname. |
| 50 | + 2. If locale detection is enabled and the request targets the index route, detect locale based |
| 51 | + on the `NEXT_LOCALE` cookie or `Accept-Language` header and redirect to the locale-prefixed |
| 52 | + path (or root for default locale). TODO: avoid redirect loops. |
| 53 | + 3. If the pathname has no locale prefix, add the default locale prefix to the pathname (index |
| 54 | + special case) and continue. |
| 55 | + 4. `[onBuildComplete routes]` Collect headers (from `next.config`) that match current request |
| 56 | + conditions and apply them to the final response. |
| 57 | + 5. `[onBuildComplete routes]` Non-priority redirects (from `next.config`). If matched, terminate. |
| 58 | + 6. `[onBuildComplete routes]` If middleware matched: run middleware. Note: middleware responses |
| 59 | + may be non-terminal (they can rewrite the request or mutate headers). Implementers must handle |
| 60 | + `x-middleware-rewrite`, `x-middleware-next`, `x-middleware-override-headers`, |
| 61 | + `x-middleware-set-cookie`, and similar control headers. |
| 62 | + 7. `[onBuildComplete routes]` Run `beforeFiles` rewrites (from `next.config`); on a match, |
| 63 | + continue. |
| 64 | + 8. `[generic rules]` Ensure that `/404` or `/<locale>/404` (if i18n enabled) routes return a 404 |
| 65 | + status for non-revalidate requests, then continue. |
| 66 | + 9. `[generic rules]` Ensure that `/500` or `/<locale>/500` (if i18n enabled) routes return a 500 |
| 67 | + status for non-revalidate requests, then continue. |
| 68 | + 10. `[generic rules]` If middleware/proxy is enabled and using the Pages router: |
| 69 | + 1. If the request has the `x-next-data` header, rewrite the request path to |
| 70 | + `<basepath>/_next/data/<build_id>/<path>.json` (index special case) and continue — this |
| 71 | + undoes the `_next/data` path normalization done earlier. |
| 72 | + 11. `[generic rules]` If App router behavior applies: |
| 73 | + 1. Prefetch/segment handling is required here — it depends on multiple request headers and |
| 74 | + needs to be fleshed out (placeholder). |
| 75 | + 2. If the request has an `rsc: 1` header, rewrite the request path to `<basepath>/<path>.rsc` |
| 76 | + (index special case) and continue. |
| 77 | + 12. Proceed to the Output matching phase. |
| 78 | + |
| 79 | +2. Output matching phase |
| 80 | + |
| 81 | + This phase executes potentially multiple times during the request chain. It's often invoked after |
| 82 | + applying a rewrite to check whether the rewrite resulted in a match on outputs. |
| 83 | + |
| 84 | + Steps: |
| 85 | + 1. `[onBuildComplete routes]` Try to match outputs (other than middleware) or the `_next/image` |
| 86 | + image-optimization endpoint. Prioritize prerenders over functions if both exist for the same |
| 87 | + path. Terminate on a match. TODO: decide and document priority rules if static files, |
| 88 | + prerenders, and functions overlap — define priority or declare that any overlap is unexpected. |
| 89 | + 2. `[generic rules]` Rewrite `<basepath>/_next/image` to `_next/image`. On a match, re-run output |
| 90 | + matching and terminate. |
| 91 | + 3. `[generic rules]` If middleware/proxy + Pages router is enabled (normalizing again for |
| 92 | + rewrites in future steps): |
| 93 | + 1. If the request path matches `<basepath>/_next/data/<build_id>/<path>.json`, rewrite the |
| 94 | + request path to `<basepath>/<path>` (index special case) and continue. |
| 95 | + 4. `[generic rules]` If no middleware is present and the request path matches |
| 96 | + `<basepath>/_next/data/<build_id>/<path>.json`, try matching outputs again and terminate even |
| 97 | + if not matched (return a 404). |
| 98 | + 5. `[generic rules]` If App router: rewrite `<basepath>/index.(rsc|action)` to `/` to normalize |
| 99 | + `/index` for rewrite matching. TODO: clarify prefetch/segment interactions. |
| 100 | + 6. `[onBuildComplete routes]` Run `afterFiles` rewrites (from `next.config`); on a match, re-run |
| 101 | + output matching and terminate. |
| 102 | + 7. `[generic rules]` If App router (fixing "bad rewrites"): |
| 103 | + 1. Rewrite `<basepath>/.prefetch.rsc` to `<basepath>/__index.prefetch.rsc` and re-run the |
| 104 | + filesystem phase on a match. |
| 105 | + 2. Rewrite `<basepath>/<path>/.prefetch.rsc` to `<basepath>/<path>.prefetch.rsc` and re-run |
| 106 | + the filesystem phase on a match. |
| 107 | + 3. Rewrite `<basepath>/.rsc` to `<basepath>/index.rsc` and re-run the filesystem phase on a |
| 108 | + match. |
| 109 | + 4. Rewrite `<basepath>/<path>/.rsc` to `<basepath>/<path>.rsc` and re-run the filesystem phase |
| 110 | + on a match. |
| 111 | + 8. `[generic rules]` Rewrite `<basepath>/_next/static/<path>` to |
| 112 | + `<basepath>/_next/static/not-found.txt` and assign a 404 status code. On a match, re-run |
| 113 | + output matching and terminate. |
| 114 | + 9. `[generic rules]` If i18n is enabled: |
| 115 | + 1. Strip the locale prefix from the path and try matching on outputs again; terminate on a |
| 116 | + match. |
| 117 | + 10. `[generic rules]` If middleware/proxy + Pages router is enabled: perform normalization steps |
| 118 | + as needed. |
| 119 | + 11. If the request has the `x-next-data` header, rewrite the request path to |
| 120 | + `<basepath>/_next/data/<build_id>/<path>.json` (index special case) and continue — this |
| 121 | + undoes the `_next/data` path normalization done earlier. |
| 122 | + 12. `[onBuildComplete routes]` Try to match on dynamic route rewrites (from `next.config`). On a |
| 123 | + match, check outputs again and terminate. |
| 124 | + 13. `[onBuildComplete routes]` Apply fallback rewrites (from `next.config`). On a match, check |
| 125 | + outputs again and terminate. |
| 126 | + 14. `[generic rules]` No outputs matched — return a 404 status. |
| 127 | + |
| 128 | +3. Termination |
| 129 | + |
| 130 | + After the request chain terminates (an output matched or a final status determined), apply |
| 131 | + additional transformations to the final response before returning it to the client. |
| 132 | + |
| 133 | + Steps: |
| 134 | + 1. `[generic rules]` If a matched output returns a non-error response: |
| 135 | + 1. If serving from `<basepath>/_next/static` (static files), apply |
| 136 | + `Cache-Control: public, max-age=31536000, immutable` response header. |
| 137 | + 2. Apply an `x-matched-path` response header with the matched pathname. |
| 138 | + 2. `[generic rules]` If no output matched or a 404 response was selected: |
| 139 | + 1. Serve a custom 404 page if defined in outputs: |
| 140 | + 1. If i18n + Pages router: `/<basepath>/<locale>/404` (based on the pathname locale) or |
| 141 | + `/<basepath>/<default-locale>/404` (if no locale in pathname). |
| 142 | + 2. If no i18n + Pages router: `/<basepath>/404`. |
| 143 | + 3. If App router: `/<basepath>/_not-found`. |
| 144 | + 3. `[generic rules]` If a matched output produced a 5xx response or output execution failed: |
| 145 | + 1. Serve a custom 500 page if defined in outputs: |
| 146 | + 1. If i18n + Pages router: `/<basepath>/<locale>/500` (based on the pathname locale) or |
| 147 | + `/<basepath>/<default-locale>/500` (if no locale in pathname). |
| 148 | + 2. If no i18n + Pages router: `/<basepath>/500`. |
| 149 | + 3. If App router: `/<basepath>/_error`. |
0 commit comments