Summary
In vinext@1.0.0-beta.6, a generated metadata image is considered a prerender candidate only when its default export has vinext's USE_CACHE_FUNCTION_SYMBOL. In practice, that marker is added by the callable function-level "use cache" transform.
This makes "use cache" appear necessary to prerender a static opengraph-image.tsx, even though generated metadata images in Next.js are statically optimized by default when they do not use dynamic inputs.
"use cache" is not a safe workaround for a route returning ImageResponse: it puts the Response through the callable React Flight cache, where it is not a valid return value.
Environment
- vinext: 1.0.0-beta.6
@vitejs/plugin-rsc: 0.5.34
- React / React DOM /
react-server-dom-webpack: 19.2.7
- Vite: 8.1.3
- Node.js: 22.13.1
- pnpm: 11.1.1
Next.js reference used for behavior and source comparison:
- Next.js: 16.3.0-canary.105
- commit:
ccd47cfe53c7fef5e6b7ad472a3a020605a23608
Reproduction
https://github.com/MaxtuneLee/vinext-image-response-use-cache-repro
Create a static generated metadata image without "use cache":
// app/opengraph-image.tsx
import { ImageResponse } from "next/og";
export const size = { width: 1200, height: 630 };
export default function OpenGraphImage() {
return new ImageResponse(
<div style={{ display: "flex", width: "100%", height: "100%" }}>
static metadata image
</div>,
size,
);
}
Run:
pnpm vinext build --prerender-all
Inspect dist/server/vinext-prerender.json and dist/server/prerendered-routes/.
Actual behavior
The generated metadata route is excluded from metadata prerender candidates unless its default export has Symbol.for("vinext.useCacheFunction"). A normal static opengraph-image.tsx has no such marker because it does not use the callable "use cache" transform.
As a result, a route that is otherwise static does not get the metadata response prerender artifact solely because it has no callable-cache wrapper.
Adding "use cache" makes it eligible, but changes the execution path: the ImageResponse is then passed to the callable React Flight serializer. That is a separate incorrect behavior, not a valid way to opt into metadata response caching.
Expected behavior (Next.js behavior)
Next.js statically optimizes generated opengraph-image.tsx routes by default. A static-eligible route does not need "use cache"; Next.js creates it at build time and serves it from the route response cache.
It becomes dynamic only when its code uses request-time APIs, uncached data, or explicit dynamic route configuration. This is documented in the Next.js opengraph-image file-convention documentation.
vinext should similarly:
- consider a generated metadata image as a metadata/App Route prerender candidate without requiring a callable-cache marker;
- use route dynamic-usage and cacheability state to decide whether it can be persisted;
- store the returned HTTP
Response body, status, and headers as the metadata response artifact.
Possible root cause
packages/vinext/src/server/metadata-route-response.ts uses Symbol.for("vinext.useCacheFunction") on the metadata default export as the eligibility test in getPrerenderableMetadataRoutePaths().
The symbol indicates that the function went through packages/vinext/src/plugins/use-cache-callable.ts. It is therefore a proxy for a particular implementation detail, not evidence that a metadata route is static or response-cacheable.
This conflates two independent concerns:
- whether the metadata route can be rendered at build time and its HTTP response persisted;
- whether the function's JavaScript return value is eligible for callable React Flight caching.
An ImageResponse is valid for the first concern and invalid for the second.
Suggested fix
- Remove
USE_CACHE_FUNCTION_SYMBOL as a prerequisite for metadata route prerender eligibility and metadata response caching.
- Send generated metadata routes through the normal metadata/App Route prerender path, then use dynamic-usage and cacheability signals to determine whether to write an artifact.
- Keep
buildAppRouteCacheValue(response) as the response-cache representation for generated images.
- Add an integration test for an
opengraph-image.tsx with no "use cache" that asserts a 200 response artifact is written during vinext build --prerender-all.
- Add a dynamic metadata-image test to confirm request-time APIs or uncached data still prevent static prerendering.
Summary
In
vinext@1.0.0-beta.6, a generated metadata image is considered a prerender candidate only when its default export has vinext'sUSE_CACHE_FUNCTION_SYMBOL. In practice, that marker is added by the callable function-level"use cache"transform.This makes
"use cache"appear necessary to prerender a staticopengraph-image.tsx, even though generated metadata images in Next.js are statically optimized by default when they do not use dynamic inputs."use cache"is not a safe workaround for a route returningImageResponse: it puts theResponsethrough the callable React Flight cache, where it is not a valid return value.Environment
@vitejs/plugin-rsc: 0.5.34react-server-dom-webpack: 19.2.7Next.js reference used for behavior and source comparison:
ccd47cfe53c7fef5e6b7ad472a3a020605a23608Reproduction
https://github.com/MaxtuneLee/vinext-image-response-use-cache-repro
Create a static generated metadata image without
"use cache":Run:
Inspect
dist/server/vinext-prerender.jsonanddist/server/prerendered-routes/.Actual behavior
The generated metadata route is excluded from metadata prerender candidates unless its default export has
Symbol.for("vinext.useCacheFunction"). A normal staticopengraph-image.tsxhas no such marker because it does not use the callable"use cache"transform.As a result, a route that is otherwise static does not get the metadata response prerender artifact solely because it has no callable-cache wrapper.
Adding
"use cache"makes it eligible, but changes the execution path: theImageResponseis then passed to the callable React Flight serializer. That is a separate incorrect behavior, not a valid way to opt into metadata response caching.Expected behavior (Next.js behavior)
Next.js statically optimizes generated
opengraph-image.tsxroutes by default. A static-eligible route does not need"use cache"; Next.js creates it at build time and serves it from the route response cache.It becomes dynamic only when its code uses request-time APIs, uncached data, or explicit dynamic route configuration. This is documented in the Next.js
opengraph-imagefile-convention documentation.vinext should similarly:
Responsebody, status, and headers as the metadata response artifact.Possible root cause
packages/vinext/src/server/metadata-route-response.tsusesSymbol.for("vinext.useCacheFunction")on the metadata default export as the eligibility test ingetPrerenderableMetadataRoutePaths().The symbol indicates that the function went through
packages/vinext/src/plugins/use-cache-callable.ts. It is therefore a proxy for a particular implementation detail, not evidence that a metadata route is static or response-cacheable.This conflates two independent concerns:
An
ImageResponseis valid for the first concern and invalid for the second.Suggested fix
USE_CACHE_FUNCTION_SYMBOLas a prerequisite for metadata route prerender eligibility and metadata response caching.buildAppRouteCacheValue(response)as the response-cache representation for generated images.opengraph-image.tsxwith no"use cache"that asserts a 200 response artifact is written duringvinext build --prerender-all.