Return plain text 404 for non-HTML subresource requests to unknown paths - #89
Draft
gaearon wants to merge 4 commits into
Draft
Return plain text 404 for non-HTML subresource requests to unknown paths#89gaearon wants to merge 4 commits into
gaearon wants to merge 4 commits into
Conversation
🦋 Changeset detectedLatest commit: 40807f6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Sec-Fetch-Dest lets us tell that a request for e.g. a missing web-app-manifest icon can never render HTML, so route it to the shared plain text not-found asset in the error phase instead of paying for the app's not-found output.
This repo intentionally has no local test setup (see #2) in favor of relying on the main vercel/next.js repo's test suite against real deployments. A vitest-based unit test reintroduces exactly the local setup that was removed; coverage for this route instead comes from next.js e2e tests run in adapter deploy mode.
Drifting from the vercel/next.js list isn't a correctness problem, just a missed optimization, so say that instead of "keep in sync".
Keeps this route consistent with the /_next/static/ 404 route fixed on main, instead of duplicating the header object with a stale copy that's missing cache-control.
gaearon
force-pushed
the
gaearon-not-found
branch
from
July 29, 2026 20:17
5fbd9a3 to
40807f6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Companion to vercel/next.js#95930, which uses
Sec-Fetch-Destto serve a plain text 404 for subresource requests (images, fonts, manifests, scripts, etc.) to unknown paths instead of rendering the app'snot-foundoutput.That fix only covers
next dev/next start/minimal mode — deployments on this adapter still returned the full prerendered not-found page for these requests, since the CDN routing layer serves it directly without invoking Next.js.This closes that gap at the routing layer.
Adds a route in the
errorphase, right after{ handle: 'error' }and before the existing not-found dispatch, matching the sameSec-Fetch-Destvalues asis-non-html-sec-fetch-dest.tsinvercel/next.js, and serving the shared_next/static/not-found.txtasset withtext/plain+404instead.Stacked on #93, which fixes the same missing-
cache-controlbug on the pre-existing/_next/static/404 route and extracts a sharedNOT_FOUND_TXT_HEADERSconstant. This PR's new route reuses that constant instead of duplicating the header object.No local unit test — this repo relies on
vercel/next.js's e2e suite against real deployments (see #2); coverage comes fromnot-found-non-document.test.tsandnot-found-non-document-dynamic.test.ts, which hadisNextDeployTODOs for exactly this gap.Verification
The real CI deploy job (
NEXT_ENABLE_ADAPTER=1) can't exercise an unpublished branch — it just selects a Vercel team whose build infra runs whatever adapter version is pinned platform-side.So I ran the actual
vercel/next.jsJest deploy suites locally against this branch: packed this branch into a tarball, wired it intonot-found-non-document.test.tsandnot-found-non-document-dynamic.test.tsviaadapterPath+ afile:dependency, and ranpnpm test-deploy-turbofor real against fresh Vercel deployments (not scratch curl checks).The full matrix is static/dynamic fixture ×
cacheComponentson/off. All four cells go through the new routing rule — I initially wrote off the dynamic/cacheComponents: falsecell as "already correct pre-fix," but that was wrong: it hits the same route as the rest, and before the fix the plain-text response'scache-controlwasn't actually overridden, it just inherited the CDN's default for the staticnot-found.txtasset.cacheComponents: falsecacheComponents: truecacheComponents: falsecell, including a path never previously requestedManual test plan
Live scratch deployments of this branch (rebuilt with the #93 fix included) for the two matrix rows:
/_not-found— https://verify-todo-static-cc.vercel.app/_not-foundundercacheComponents: true— https://verify-todo-dynamic-manifest.vercel.appOpen either URL with browser dev tools open on the Network tab, then reload. The page loads a missing image, a missing
@font-face, and a missing web app manifest, so the browser itself issuesimage/font/manifestSec-Fetch-Destrequests to unknown paths — no header spoofing needed.For each of
missing-icon.png,missing-font.woff2,site.webmanifest: response status404,content-type: text/plain, bodyNot Found,cache-control: private, no-cache, no-store, max-age=0, must-revalidate(matching the header Next.js itself sets for this response — seerouter-server.tsinvercel/next.js).Then navigate to
/does-not-existdirectly (document request) — still renders the custom not-found page astext/html, unaffected.