mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[GH-ISSUE #884] Metadata file routes (icon, apple-icon, opengraph-image, twitter-image) are served at the wrong URL — extension is stripped #197
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#197
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @eashish93 on GitHub (Apr 24, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/884
Summary
vinext serves App Router metadata files (
app/icon.*,app/apple-icon.*,app/opengraph-image.*,app/twitter-image.*) at extensionless URLs (/icon,/apple-icon, etc.), whereas Next.js serves them at URLs that preserve the source extension (/icon.png,/apple-icon.png, …) with a cache-busting query string.This difference silently breaks any project using the common Next.js middleware matcher pattern, because that pattern excludes requests by file extension. vinext's extensionless URLs fall through to protected-route logic and get redirected to
/login(or equivalent), so icons never render.Next.js behavior (for reference)
Build output of a real Next.js 16 project with
app/icon.png,app/apple-icon.png,app/favicon.ico:routes-manifest.json:Rendered HTML
<head>:Two important properties:
/icon.png, not/icon).vinext behavior (current)
dist/server/metadata-routes.js:With
app/icon.svgin a project, the SSR<head>contains:And requesting
/icondirectly:No hash, no extension.
Why this breaks real projects
The middleware matcher pattern recommended by Next.js docs (and used verbatim in many projects, including minform and kitful) excludes assets by file extension:
Under this matcher:
/icon.png?<hash>matches[^.]*\.png, so middleware is skipped — icon loads./iconhas no extension, so the matcher runs middleware — auth redirect fires — icon request gets a 307 to/login?redirectURI=%2Ficon— browser receives HTML instead of an image — Firefox/Safari fail to render the icon (Chrome masks it by falling back to root/favicon.icoauto-discovery, so this bug can look deceptively browser-specific).This is the root cause of https://github.com/cloudflare/vinext/issues/… (if a related "favicon not loading" report exists).
Reproduction
app/icon.svg(oricon.png) andapp/favicon.icointoapp/./login, using the common matcher above. (Or just anymiddleware.tswhose matcher doesn't explicitly exclude/icon.)<head>— icon<link>points to/icon(no extension).curl -I http://localhost:<port>/icon→ 307 to/login.Expected:
/icon.svg?<hash>(or/icon.png?<hash>), matching Next.js.Suggested fix
In
src/server/metadata-routes.ts:urlPathinMETADATA_FILE_MAPto be a template (or computed from the discovered file's extension). For theicon,apple-icon,opengraph-image,twitter-imagetypes, emit/<basename>.<ext>rather than/<basename>.favicon, keep/favicon.ico(already matches Next.js).<link>tag to match Next.js behavior.icon.pngandicon.svg), emit one route per extension and one<link>tag per file — same as Next.js.Impact
Any project using the default-style Next.js middleware matcher (i.e., most real projects with authentication) will see:
/favicon.icoauto-discovery — works by accident but hides the bug during development.apple-icon,opengraph-image, andtwitter-imagefor social previews (crawlers don't do Chrome-style/favicon.icofallback).Workaround
Either:
icon|apple-icon|opengraph-image|twitter-imageto the middleware matcher exclusion list, ormetadata.iconsexplicitly inapp/layout.tsxand rely on the browser's root/favicon.icoauto-discovery for the tab favicon.Both feel like papering over a behavior gap rather than a real fix.