Repository: vercel/next.js
Pull request: Remove workStore from metadata resolution chain

Stacked on #90215

workStore was threaded through 6 functions in resolve-metadata.ts and the entire createMetadataComponents closure but only used once for workStore.route in accumulateMetadata. It now reads from workAsyncStorage.getStore() at that single usage site instead. This eliminates workStore from the createMetadataComponents parameter list entirely, bringing it one step closer to being replaceable with plain components.

Changed files:
filename: packages/next/src/lib/metadata/metadata.tsx
patch: @@ -14,7 +14,6 @@ import type {
 } from './types/metadata-interface'
 import { isHTTPAccessFallbackError } from '../../client/components/http-access-fallback/http-access-fallback'
 import type { MetadataContext } from './types/resolvers'
-import type { WorkStore } from '../../server/app-render/work-async-storage.external'
 import { createServerSearchParamsForMetadata } from '../../server/request/search-params'
 import { createServerPathnameForMetadata } from '../../server/request/pathname'
 import { isPostpone } from '../../server/lib/router-utils/is-postpone'
@@ -41,7 +40,6 @@ export function createMetadataComponents({
   metadataContext,
   getDynamicParamFromSegment,
   errorType,
-  workStore,
   serveStreamingMetadata,
 }: {
   tree: LoaderTree
@@ -50,7 +48,6 @@ export function createMetadataComponents({
   metadataContext: MetadataContext
   getDynamicParamFromSegment: GetDynamicParamFromSegment
   errorType?: MetadataErrorType | 'redirect'
-  workStore: WorkStore
   serveStreamingMetadata: boolean
 }): {
   Viewport: React.ComponentType
@@ -65,7 +62,6 @@ export function createMetadataComponents({
       tree,
       searchParams,
       getDynamicParamFromSegment,
-      workStore,
       errorType
     ).catch((viewportErr) => {
       // When Legacy PPR is enabled viewport can reject with a Postpone type
@@ -78,8 +74,7 @@ export function createMetadataComponents({
         return getNotFoundViewport(
           tree,
           searchParams,
-          getDynamicParamFromSegment,
-          workStore
+          getDynamicParamFromSegment
         ).catch(() => null)
       }
       // We're going to throw the error from the metadata outlet so we just render null here instead
@@ -105,7 +100,6 @@ export function createMetadataComponents({
       searchParams,
       getDynamicParamFromSegment,
       metadataContext,
-      workStore,
       errorType
     ).catch((metadataErr) => {
       // When Legacy PPR is enabled metadata can reject with a Postpone type
@@ -120,8 +114,7 @@ export function createMetadataComponents({
           pathnameForMetadata,
           searchParams,
           getDynamicParamFromSegment,
-          metadataContext,
-          workStore
+          metadataContext
         ).catch(() => null)
       }
       // We're going to throw the error from the metadata outlet so we just render null here instead
@@ -162,14 +155,12 @@ export function createMetadataComponents({
         searchParams,
         getDynamicParamFromSegment,
         metadataContext,
-        workStore,
         errorType
       ),
       getResolvedViewport(
         tree,
         searchParams,
         getDynamicParamFromSegment,
-        workStore,
         errorType
       ),
     ]).then(() => null)
@@ -202,7 +193,6 @@ async function getResolvedMetadataImpl(
   searchParams: Promise<ParsedUrlQuery>,
   getDynamicParamFromSegment: GetDynamicParamFromSegment,
   metadataContext: MetadataContext,
-  workStore: WorkStore,
   errorType?: MetadataErrorType | 'redirect'
 ): Promise<React.ReactNode> {
   const errorConvention = errorType === 'redirect' ? undefined : errorType
@@ -212,7 +202,6 @@ async function getResolvedMetadataImpl(
     searchParams,
     getDynamicParamFromSegment,
     metadataContext,
-    workStore,
     errorConvention
   )
 }
@@ -223,8 +212,7 @@ async function getNotFoundMetadataImpl(
   pathname: Promise<string>,
   searchParams: Promise<ParsedUrlQuery>,
   getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  metadataContext: MetadataContext,
-  workStore: WorkStore
+  metadataContext: MetadataContext
 ): Promise<React.ReactNode> {
   const notFoundErrorConvention = 'not-found'
   return renderMetadata(
@@ -233,7 +221,6 @@ async function getNotFoundMetadataImpl(
     searchParams,
     getDynamicParamFromSegment,
     metadataContext,
-    workStore,
     notFoundErrorConvention
   )
 }
@@ -243,15 +230,13 @@ async function getResolvedViewportImpl(
   tree: LoaderTree,
   searchParams: Promise<ParsedUrlQuery>,
   getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore,
   errorType?: MetadataErrorType | 'redirect'
 ): Promise<React.ReactNode> {
   const errorConvention = errorType === 'redirect' ? undefined : errorType
   return renderViewport(
     tree,
     searchParams,
     getDynamicParamFromSegment,
-    workStore,
     errorConvention
   )
 }
@@ -260,15 +245,13 @@ const getNotFoundViewport = cache(getNotFoundViewportImpl)
 async function getNotFoundViewportImpl(
   tree: LoaderTree,
   searchParams: Promise<ParsedUrlQuery>,
-  getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore
+  getDynamicParamFromSegment: GetDynamicParamFromSegment
 ): Promise<React.ReactNode> {
   const notFoundErrorConvention = 'not-found'
   return renderViewport(
     tree,
     searchParams,
     getDynamicParamFromSegment,
-    workStore,
     notFoundErrorConvention
   )
 }
@@ -279,7 +262,6 @@ async function renderMetadata(
   searchParams: Promise<ParsedUrlQuery>,
   getDynamicParamFromSegment: GetDynamicParamFromSegment,
   metadataContext: MetadataContext,
-  workStore: WorkStore,
   errorConvention?: MetadataErrorType
 ) {
   const resolvedMetadata = await resolveMetadata(
@@ -288,7 +270,6 @@ async function renderMetadata(
     searchParams,
     errorConvention,
     getDynamicParamFromSegment,
-    workStore,
     metadataContext
   )
   return <>{createMetadataElements(resolvedMetadata)}</>
@@ -298,15 +279,13 @@ async function renderViewport(
   tree: LoaderTree,
   searchParams: Promise<ParsedUrlQuery>,
   getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore,
   errorConvention?: MetadataErrorType
 ) {
   const resolvedViewport = await resolveViewport(
     tree,
     searchParams,
     errorConvention,
-    getDynamicParamFromSegment,
-    workStore
+    getDynamicParamFromSegment
   )
   return <>{createViewportElements(resolvedViewport)}</>
 }

filename: packages/next/src/lib/metadata/resolve-metadata.ts
patch: @@ -21,7 +21,8 @@ import type {
 } from './types/metadata-types'
 import type { ParsedUrlQuery } from 'querystring'
 import type { StaticMetadata } from './types/icons'
-import type { WorkStore } from '../../server/app-render/work-async-storage.external'
+import { workAsyncStorage } from '../../server/app-render/work-async-storage.external'
+import { InvariantError } from '../../shared/lib/invariant-error'
 import type { Params } from '../../server/request/params'
 import type { SearchParams } from '../../server/request/search-params'
 
@@ -708,8 +709,7 @@ const resolveMetadataItems = cache(async function (
   tree: LoaderTree,
   searchParams: Promise<ParsedUrlQuery>,
   errorConvention: MetadataErrorType | undefined,
-  getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore
+  getDynamicParamFromSegment: GetDynamicParamFromSegment
 ) {
   const parentParams = {}
   const metadataItems: MetadataItems = []
@@ -723,8 +723,7 @@ const resolveMetadataItems = cache(async function (
     searchParams,
     errorConvention,
     errorMetadataItem,
-    getDynamicParamFromSegment,
-    workStore
+    getDynamicParamFromSegment
   )
 })
 
@@ -737,8 +736,7 @@ async function resolveMetadataItemsImpl(
   searchParams: Promise<ParsedUrlQuery>,
   errorConvention: MetadataErrorType | undefined,
   errorMetadataItem: MetadataItems[number],
-  getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore
+  getDynamicParamFromSegment: GetDynamicParamFromSegment
 ): Promise<MetadataItems> {
   const [segment, parallelRoutes, { page }] = tree
   const currentTreePrefix =
@@ -783,8 +781,7 @@ async function resolveMetadataItemsImpl(
       searchParams,
       errorConvention,
       errorMetadataItem,
-      getDynamicParamFromSegment,
-      workStore
+      getDynamicParamFromSegment
     )
   }
 
@@ -802,8 +799,7 @@ const resolveViewportItems = cache(async function (
   tree: LoaderTree,
   searchParams: Promise<ParsedUrlQuery>,
   errorConvention: MetadataErrorType | undefined,
-  getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore
+  getDynamicParamFromSegment: GetDynamicParamFromSegment
 ) {
   const parentParams = {}
   const viewportItems: ViewportItems = []
@@ -819,8 +815,7 @@ const resolveViewportItems = cache(async function (
     searchParams,
     errorConvention,
     errorViewportItemRef,
-    getDynamicParamFromSegment,
-    workStore
+    getDynamicParamFromSegment
   )
 })
 
@@ -833,8 +828,7 @@ async function resolveViewportItemsImpl(
   searchParams: Promise<ParsedUrlQuery>,
   errorConvention: MetadataErrorType | undefined,
   errorViewportItemRef: ErrorViewportItemRef,
-  getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore
+  getDynamicParamFromSegment: GetDynamicParamFromSegment
 ): Promise<ViewportItems> {
   const [segment, parallelRoutes, { page }] = tree
   const currentTreePrefix =
@@ -890,8 +884,7 @@ async function resolveViewportItemsImpl(
       searchParams,
       errorConvention,
       errorViewportItemRef,
-      getDynamicParamFromSegment,
-      workStore
+      getDynamicParamFromSegment
     )
   }
 
@@ -1260,16 +1253,18 @@ export async function resolveMetadata(
   searchParams: Promise<ParsedUrlQuery>,
   errorConvention: MetadataErrorType | undefined,
   getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore,
   metadataContext: MetadataContext
 ): Promise<ResolvedMetadata> {
   const metadataItems = await resolveMetadataItems(
     tree,
     searchParams,
     errorConvention,
-    getDynamicParamFromSegment,
-    workStore
+    getDynamicParamFromSegment
   )
+  const workStore = workAsyncStorage.getStore()
+  if (!workStore) {
+    throw new InvariantError('Expected workStore to be initialized')
+  }
   return accumulateMetadata(
     workStore.route,
     metadataItems,
@@ -1283,15 +1278,13 @@ export async function resolveViewport(
   tree: LoaderTree,
   searchParams: Promise<ParsedUrlQuery>,
   errorConvention: MetadataErrorType | undefined,
-  getDynamicParamFromSegment: GetDynamicParamFromSegment,
-  workStore: WorkStore
+  getDynamicParamFromSegment: GetDynamicParamFromSegment
 ): Promise<ResolvedViewport> {
   const viewportItems = await resolveViewportItems(
     tree,
     searchParams,
     errorConvention,
-    getDynamicParamFromSegment,
-    workStore
+    getDynamicParamFromSegment
   )
   return accumulateViewport(viewportItems)
 }

filename: packages/next/src/server/app-render/app-render.tsx
patch: @@ -550,7 +550,6 @@ async function generateDynamicRSCPayload(
       pathname: url.pathname,
       metadataContext: createMetadataContext(ctx.renderOpts),
       getDynamicParamFromSegment,
-      workStore,
       serveStreamingMetadata,
     })
 
@@ -1444,7 +1443,6 @@ async function getRSCPayload(
     pathname: url.pathname,
     metadataContext: createMetadataContext(ctx.renderOpts),
     getDynamicParamFromSegment,
-    workStore,
     serveStreamingMetadata,
   })
 
@@ -1565,7 +1563,6 @@ async function getErrorRSCPayload(
     metadataContext: createMetadataContext(ctx.renderOpts),
     errorType,
     getDynamicParamFromSegment,
-    workStore,
     serveStreamingMetadata: serveStreamingMetadata,
   })