Files
next.js/test/e2e/app-dir/metadata-static-file/utils.ts
1482af898d [dev] Serve static metadata from filesystem (#83460)
Current metadata files are compiled to `route.js` files. This happens
not only to metadata API (e.g., `icon.tsx`), but to static files as
well, which is unnecessary. In development, this leads to confusing logs
such as:

```
Compiled /favicon.ico in ...ms
```

Users may reasonably wonder why Next.js is "compiling" a static file.

Static files can be served directly from the filesystem, like
`_next/static/` assets. Therefore, in development, skip compilation of
static metadata files and serve them as static.

For production, we can follow up in a separate PR to skip compiling the
`route.js` files and emit static files to the dist directory, and also
serve from the filesystem there as well.

Closes [NEXT-4338](https://linear.app/vercel/issue/NEXT-4338)

---

TODO(s) to follow up:

- Fix Cache Components failing build with metadata files inside the
dynamic route.
- x-ref: [slack
thread](https://vercel.slack.com/archives/C06N3AXRQ82/p1738053808940719)

---------

Co-authored-by: Jiachi Liu <[email protected]>
2025-09-10 14:25:41 +02:00

81 lines
2.3 KiB
TypeScript

// @ts-nocheck - On isolated test, this will be a type error.
import type { Playwright } from '../../../lib/next-webdriver'
async function getMetadataLinks(browser: Playwright) {
const links = await browser.locator('link').evaluateAll((elements: any[]) => {
return elements
.filter((el) => {
if (el.href.includes('/_next/static')) {
return false
}
return [
'/favicon.ico',
'/manifest.json',
'/manifest.webmanifest',
// Below may have suffixes like /icon1.png, /icon2.png, etc.
// Or has suffixes like /icon-xxxxxx.png, /icon-image-yyyyyy.jpg, etc.
'/icon',
'/apple-icon',
'/opengraph-image',
'/twitter-image',
].some((file) =>
new URL(el.href, window.location.origin).pathname.includes(file)
)
})
.map((el) => ({
href: new URL(el.href, window.location.origin).pathname,
rel: el.rel,
...(el.type && { type: el.type }),
}))
.sort((a, b) => a.href.localeCompare(b.href))
})
return links
}
async function getMetadataMetas(browser: Playwright) {
const metas = await browser.locator('meta').evaluateAll((elements: any[]) => {
return elements
.filter((meta) => {
if (!meta.name && !meta.hasAttribute('property')) {
return false
}
const attr = meta.name || meta.getAttribute('property') || ''
return [
'og:',
'twitter:',
'viewport',
'description',
'keywords',
'robots',
].some(
(prefix) => attr.startsWith(prefix) || attr === prefix.slice(0, -1)
)
})
.map((el) => ({
...(el.name && { name: el.name }),
...(el.hasAttribute('property') && {
property: el.getAttribute('property'),
}),
}))
.sort((a, b) => {
if (a.name && !b.name) return -1
if (!a.name && b.name) return 1
return (a.name || a.property || '').localeCompare(
b.name || b.property || ''
)
})
})
return metas
}
export async function getCommonMetadataHeadTags(browser: Playwright) {
const [links, metas] = await Promise.all([
getMetadataLinks(browser),
getMetadataMetas(browser),
])
return { links, metas }
}