@rsc-kit/core 0.30.5 → 0.30.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"notFound.js","sourceRoot":"","sources":["../src/notFound.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,yDAAyD;AACzD,uDAAuD;AACvD,4BAA4B;AAC5B,UAAU;AACV,MAAM;AACN,EAAE;AACF,0EAA0E;AAC1E,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,yEAAyE;AACzE,kDAAkD;AAClD,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,uCAAuC;AACvC,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,+CAA+C;AAC/C,6EAA6E;AAC7E,wEAAwE;AAExE;;;;;;;;;GASG;AACH,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAA;AAEzD;;;;;;;;;;;;;GAaG;AACH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAA;AAExD,SAAS,MAAM;IACb,MAAM,KAAK,GAAI,UAAsC,CAAC,KAAK,CAE9C,CAAA;IAEb,MAAM,KAAK,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAA;IAE/B,IAAI,KAAK;QAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAA;AAClC,CAAC;AAED,oEAAoE;AACpE,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,GAAG,WAAW;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAC3B;QAAC,IAA2C,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;QAE1D,wEAAwE;QACxE,uEAAuE;QACvE,gDAAgD;QAChD,MAAM,EAAE,CAAA;IACV,CAAC;CACF;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAAiC,CAAC,IAAI,CAAC,KAAK,IAAI,CACjG,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,GAAG,eAAe,CAAA;AAE9B,kEAAkE;AAClE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;AAChD,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,OAAO,MAAM,KAAK,MAAM,CAAA;AAC1B,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,IAAI,cAAc,EAAE,CAAA;AAC5B,CAAC","sourcesContent":["// Saying a url does not name a page, from inside a render.\n//\n// import { notFound } from '@rsc-kit/core/not-found'\n//\n// export default async function PostPage({ params }) {\n// const post = await findPost((await params).slug)\n// if (!post) notFound()\n// ...\n// }\n//\n// Until this existed, \"no such page\" could only be decided by the router,\n// before anything rendered. But most of the time it is not knowable then: the\n// route matched, the url is well-formed, and whether the thing exists is a\n// question only the page can ask. The alternative was an error boundary and a\n// 500, which tells a crawler the page is broken rather than absent.\n//\n// Delivery follows the same two windows a redirect has, and for the same\n// reason — headers flush when the shell resolves:\n//\n// Before the shell — nothing is written yet, so the host answers 404 and\n// renders not-found.tsx. This is where a params check lands, and where a\n// lookup at the top of a page lands.\n//\n// After the shell — the status line is gone. React carries an error digest\n// for every server error, so the mark travels there and the boundary around\n// it shows its fallback rather than a stack.\n// The page ends with a noindex tag, and a crawler - which is answered with\n// the finished document, see crawler.ts - gets a real 404 regardless.\n\n/**\n * The mark that says an error is a missing page.\n *\n * A property rather than `instanceof`, for the reason this project keeps\n * meeting: the app's components are bundled apart from the engine that renders\n * them, so each side evaluates its own copy of this module and its own copy of\n * the class. `instanceof` is simply false across that seam — the page would\n * render as an unhandled error, and the only clue would be a 500 where a 404\n * was meant.\n */\nconst MARK = Symbol.for('@rsc-kit/core.not-found-signal')\n\n/**\n * Where a render records that it wants the not-found page.\n *\n * The same scope a redirect records in, reached through the well-known symbol\n * rather than by importing it: this module is evaluated in the app bundle, the\n * engine and the browser, and only one of those should be pulling in the\n * redirect machinery.\n *\n * Recording is necessary because the throw alone does not survive. React\n * catches what a component threw and re-raises its own error, whose message is\n * stripped in production — so the host testing the caught value would work in\n * development and answer 500 in production, which is the worst of the two\n * possible bugs.\n */\nconst SCOPE = Symbol.for('@rsc-kit/core.redirect-scope')\n\nfunction record(): void {\n const scope = (globalThis as Record<symbol, unknown>)[SCOPE] as\n | { getStore(): { notFound?: boolean } | undefined }\n | undefined\n\n const store = scope?.getStore()\n\n if (store) store.notFound = true\n}\n\n/** Thrown to stop rendering a page whose subject does not exist. */\nexport class NotFoundSignal extends Error {\n constructor(message = 'Not found') {\n super(message)\n this.name = 'NotFoundSignal'\n ;(this as unknown as Record<symbol, boolean>)[MARK] = true\n\n // In the constructor rather than in notFound(), so every way of raising\n // one is recorded — a params schema refusing is not routed through the\n // public function and must answer the same way.\n record()\n }\n}\n\n/** Whether this is a missing page, whichever copy of the class threw it. */\nexport function isNotFoundSignal(error: unknown): error is NotFoundSignal {\n return (\n typeof error === 'object' && error !== null && (error as Record<symbol, unknown>)[MARK] === true\n )\n}\n\n/**\n * The prefix that carries a missing page inside React's error digest.\n *\n * React replaces a server error's message with an opaque digest in production,\n * and the digest is the one part it is guaranteed to transmit. Kept distinct\n * from the redirect prefix so neither is ever read as the other.\n */\nconst PREFIX = 'RSC_NOT_FOUND'\n\n/** The digest for a missing page, or null for any other error. */\nexport function notFoundDigest(error: unknown): string | null {\n return isNotFoundSignal(error) ? PREFIX : null\n}\n\n/** Whether a digest describes a missing page. */\nexport function isNotFoundDigest(digest: unknown): boolean {\n return digest === PREFIX\n}\n\n/**\n * Answer this url with the not-found page.\n *\n * Never returns: it throws, which stops the component that called it. Do not\n * wrap a call in `try`/`catch` without rethrowing what you do not recognise —\n * swallowing this turns a missing page into a blank region.\n *\n * Called above every Suspense boundary, the response is a real 404 carrying\n * not-found.tsx. Called inside one, the shell is already on the wire, so the\n * boundary shows its fallback, the status stays 200 and the page ends with a\n * noindex tag. A search engine is answered with the finished page, so it\n * gets a real 404 wherever this was called.\n */\nexport function notFound(): never {\n throw new NotFoundSignal()\n}\n"]}
1
+ {"version":3,"file":"notFound.js","sourceRoot":"","sources":["../src/notFound.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,yDAAyD;AACzD,uDAAuD;AACvD,4BAA4B;AAC5B,UAAU;AACV,MAAM;AACN,EAAE;AACF,0EAA0E;AAC1E,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,oEAAoE;AACpE,EAAE;AACF,yEAAyE;AACzE,kDAAkD;AAClD,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,uCAAuC;AACvC,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAC9E,6EAA6E;AAC7E,wEAAwE;AAExE;;;;;;;;;GASG;AACH,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAA;AAEzD;;;;;;;;;;;;;GAaG;AACH,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAA;AAExD,SAAS,MAAM;IACb,MAAM,KAAK,GAAI,UAAsC,CAAC,KAAK,CAE9C,CAAA;IAEb,MAAM,KAAK,GAAG,KAAK,EAAE,QAAQ,EAAE,CAAA;IAE/B,IAAI,KAAK;QAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAA;AAClC,CAAC;AAED,oEAAoE;AACpE,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,GAAG,WAAW;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAC3B;QAAC,IAA2C,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;QAE1D,wEAAwE;QACxE,uEAAuE;QACvE,gDAAgD;QAChD,MAAM,EAAE,CAAA;IACV,CAAC;CACF;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAAiC,CAAC,IAAI,CAAC,KAAK,IAAI,CACjG,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,GAAG,eAAe,CAAA;AAE9B,kEAAkE;AAClE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;AAChD,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,OAAO,MAAM,KAAK,MAAM,CAAA;AAC1B,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,QAAQ;IACtB,MAAM,IAAI,cAAc,EAAE,CAAA;AAC5B,CAAC","sourcesContent":["// Saying a url does not name a page, from inside a render.\n//\n// import { notFound } from '@rsc-kit/core/not-found'\n//\n// export default async function PostPage({ params }) {\n// const post = await findPost((await params).slug)\n// if (!post) notFound()\n// ...\n// }\n//\n// Until this existed, \"no such page\" could only be decided by the router,\n// before anything rendered. But most of the time it is not knowable then: the\n// route matched, the url is well-formed, and whether the thing exists is a\n// question only the page can ask. The alternative was an error boundary and a\n// 500, which tells a crawler the page is broken rather than absent.\n//\n// Delivery follows the same two windows a redirect has, and for the same\n// reason — headers flush when the shell resolves:\n//\n// Before the shell — nothing is written yet, so the host answers 404 and\n// renders not-found.tsx. This is where a params check lands, and where a\n// lookup at the top of a page lands.\n//\n// After the shell — the status line is gone. React carries an error digest\n// for every server error, so the mark travels there. The browser's boundary\n// takes it for the page's answer, not a failure, and asks the server for\n// not-found.tsx to put where the page was - layouts kept, the url as it is.\n// The page ends with a noindex tag, and a crawler - which is answered with\n// the finished document, see crawler.ts - gets a real 404 regardless.\n\n/**\n * The mark that says an error is a missing page.\n *\n * A property rather than `instanceof`, for the reason this project keeps\n * meeting: the app's components are bundled apart from the engine that renders\n * them, so each side evaluates its own copy of this module and its own copy of\n * the class. `instanceof` is simply false across that seam — the page would\n * render as an unhandled error, and the only clue would be a 500 where a 404\n * was meant.\n */\nconst MARK = Symbol.for('@rsc-kit/core.not-found-signal')\n\n/**\n * Where a render records that it wants the not-found page.\n *\n * The same scope a redirect records in, reached through the well-known symbol\n * rather than by importing it: this module is evaluated in the app bundle, the\n * engine and the browser, and only one of those should be pulling in the\n * redirect machinery.\n *\n * Recording is necessary because the throw alone does not survive. React\n * catches what a component threw and re-raises its own error, whose message is\n * stripped in production — so the host testing the caught value would work in\n * development and answer 500 in production, which is the worst of the two\n * possible bugs.\n */\nconst SCOPE = Symbol.for('@rsc-kit/core.redirect-scope')\n\nfunction record(): void {\n const scope = (globalThis as Record<symbol, unknown>)[SCOPE] as\n | { getStore(): { notFound?: boolean } | undefined }\n | undefined\n\n const store = scope?.getStore()\n\n if (store) store.notFound = true\n}\n\n/** Thrown to stop rendering a page whose subject does not exist. */\nexport class NotFoundSignal extends Error {\n constructor(message = 'Not found') {\n super(message)\n this.name = 'NotFoundSignal'\n ;(this as unknown as Record<symbol, boolean>)[MARK] = true\n\n // In the constructor rather than in notFound(), so every way of raising\n // one is recorded — a params schema refusing is not routed through the\n // public function and must answer the same way.\n record()\n }\n}\n\n/** Whether this is a missing page, whichever copy of the class threw it. */\nexport function isNotFoundSignal(error: unknown): error is NotFoundSignal {\n return (\n typeof error === 'object' && error !== null && (error as Record<symbol, unknown>)[MARK] === true\n )\n}\n\n/**\n * The prefix that carries a missing page inside React's error digest.\n *\n * React replaces a server error's message with an opaque digest in production,\n * and the digest is the one part it is guaranteed to transmit. Kept distinct\n * from the redirect prefix so neither is ever read as the other.\n */\nconst PREFIX = 'RSC_NOT_FOUND'\n\n/** The digest for a missing page, or null for any other error. */\nexport function notFoundDigest(error: unknown): string | null {\n return isNotFoundSignal(error) ? PREFIX : null\n}\n\n/** Whether a digest describes a missing page. */\nexport function isNotFoundDigest(digest: unknown): boolean {\n return digest === PREFIX\n}\n\n/**\n * Answer this url with the not-found page.\n *\n * Never returns: it throws, which stops the component that called it. Do not\n * wrap a call in `try`/`catch` without rethrowing what you do not recognise —\n * swallowing this turns a missing page into a blank region.\n *\n * Called above every Suspense boundary, the response is a real 404 carrying\n * not-found.tsx. Called inside one, the shell is already on the wire, so the\n * status stays 200 and the page ends with a noindex tag; the browser then\n * shows not-found.tsx where the page was. A search engine is answered with the finished page, so it\n * gets a real 404 wherever this was called.\n */\nexport function notFound(): never {\n throw new NotFoundSignal()\n}\n"]}
package/dist/vite.js CHANGED
@@ -703,6 +703,7 @@ function routeManifest() {
703
703
  layouts: ancestors(name, "layout").map((n) => n),
704
704
  loadings: ancestors(name, "loading").map((n) => n),
705
705
  errors: ancestors(name, "error"),
706
+ notFounds: ancestors(name, "not-found"),
706
707
  middleware: ancestors(name, "middleware").map((n) => n),
707
708
  slots,
708
709
  sections: names.filter((n) => SECTION_FILE.test(n + ".tsx") && dirOf(n) === dirOf(name)),
@@ -2953,16 +2954,22 @@ export function instrumentationImport(instrumentation) {
2953
2954
  }
2954
2955
  function generateEntryRsc(fallbackOrigin = "") {
2955
2956
  const instrumentation = instrumentationFile();
2956
- // The 404 page, if the app has one, and the layouts it renders inside.
2957
- // Computed here rather than looked up at runtime: not-found is not a route,
2958
- // so the manifest has no entry to read its chain from.
2959
- const notFoundComponent = [...components.keys()].find((name) => name.endsWith("/not-found"));
2960
- const notFoundLayouts = notFoundComponent
2961
- ? [...components.keys()]
2957
+ // Every 404 page the app has, and the layouts each renders inside. Computed
2958
+ // here rather than looked up at runtime: not-found is not a route, so the
2959
+ // manifest has no entry to read its chain from. Which one answers is decided
2960
+ // per request: the nearest above the route that said notFound(), and for a
2961
+ // url no route owns, the one at the root.
2962
+ const notFoundFiles = [...components.keys()].filter((name) => name.endsWith("/not-found") && !name.includes("/@"));
2963
+ const notFoundLayouts = {};
2964
+ for (const file of notFoundFiles) {
2965
+ notFoundLayouts[file] = [...components.keys()]
2962
2966
  .filter((name) => name.endsWith("/layout") &&
2963
- notFoundComponent.startsWith(name.slice(0, -"layout".length)))
2964
- .sort((a, b) => a.length - b.length)
2965
- : [];
2967
+ file.startsWith(name.slice(0, -"layout".length)))
2968
+ .sort((a, b) => a.length - b.length);
2969
+ }
2970
+ // The root is `app/not-found`: two segments. A nested one alone does not
2971
+ // answer a url nothing owns - it belongs to the routes beneath it.
2972
+ const rootNotFound = notFoundFiles.find((name) => name.split("/").length === 2) ?? null;
2966
2973
  const imports = [];
2967
2974
  const mapEntries = [];
2968
2975
  const metaEntries = [];
@@ -3035,14 +3042,14 @@ import { notFoundDigest, isNotFoundSignal } from ${JSON.stringify(join(packageDi
3035
3042
  import { noteRequestRead, notePageProps, urlOf } from ${JSON.stringify(join(packageDir, "request"))}
3036
3043
  import { redirectDigest } from ${JSON.stringify(join(packageDir, "redirectDigest"))}
3037
3044
  import { cancelledByConsumer } from ${JSON.stringify(join(packageDir, "js/fallbackReport"))}
3038
- import { createRscHandler, pageSaidNotFound, queryAndParams } from ${JSON.stringify(join(packageDir, "host"))}
3045
+ import { createRscHandler, notFoundRouteOf, pageSaidNotFound, queryAndParams } from ${JSON.stringify(join(packageDir, "host"))}
3039
3046
  import { httpHostCalls } from ${JSON.stringify(join(packageDir, "hostCalls"))}
3040
3047
  import { backendVersions, installBackendVersionSource } from ${JSON.stringify(join(packageDir, "changed"))}
3041
3048
  import { RefreshOn } from ${JSON.stringify(join(packageDir, "js/refreshOn"))}
3042
3049
  import { prerenderedBeside } from ${JSON.stringify(join(packageDir, "files"))}
3043
3050
  import { renderToReadableStream, decodeReply, decodeAction, decodeFormState, loadServerAction } from '@vitejs/plugin-rsc/rsc'
3044
3051
  import { isQuery, queryCacheControl, isQueryValidationError, queryRefusal } from ${JSON.stringify(join(packageDir, "query"))}
3045
- import { isActionValidationError, isClientBuilt } from ${JSON.stringify(join(packageDir, "action"))}
3052
+ import { isActionValidationError, isActionRefusal, isClientBuilt } from ${JSON.stringify(join(packageDir, "action"))}
3046
3053
  import { forgetCached } from ${JSON.stringify(join(packageDir, "cache"))}
3047
3054
  import { noteFallback as noteCaughtRead } from ${JSON.stringify(join(packageDir, "request"))}
3048
3055
  import { isOutdatedOptimizedDep, outdatedDepResponse } from ${JSON.stringify(join(packageDir, "devReload"))}
@@ -3699,6 +3706,21 @@ function checkedSearchParams(
3699
3706
 
3700
3707
  let errorChains: Record<string, string[]> | null = null
3701
3708
 
3709
+ let notFoundChains: Record<string, string[]> | null = null
3710
+
3711
+ /** The not-found.tsx files above a component, outermost first. */
3712
+ function notFoundChain(component: string): string[] {
3713
+ if (!notFoundChains) {
3714
+ notFoundChains = {}
3715
+
3716
+ for (const route of manifest().routes as { component: string; notFounds?: string[] }[]) {
3717
+ if (route.notFounds?.length) notFoundChains[route.component] = route.notFounds
3718
+ }
3719
+ }
3720
+
3721
+ return notFoundChains[component] ?? []
3722
+ }
3723
+
3702
3724
  /** The error.tsx files above a component, outermost first. */
3703
3725
  function errorChain(component: string): string[] {
3704
3726
  if (!errorChains) {
@@ -4984,14 +5006,31 @@ export async function handleAction(
4984
5006
  // returned { validationErrors } that <Form> reads. Left thrown, React
4985
5007
  // serialises the rejection opaquely — production strips the message — and the
4986
5008
  // fields it named never reach the browser.
5009
+ //
5010
+ // A refusal is the same: the stub the build writes for a backend action is a
5011
+ // plain function, and when the backend turns the call down (Refuse(503, ...),
5012
+ // abort(429, ...), Rsc::refuse) it throws a refusal whose message was written
5013
+ // for the person asking. Its message is the result's serverError, which is
5014
+ // where a form's formError reads. Its data is not sent: there is no
5015
+ // .refusal(schema) on a plain function to check it against.
4987
5016
  let result: unknown
4988
5017
 
4989
5018
  try {
4990
5019
  result = await (action as (...a: unknown[]) => unknown)(...args)
4991
5020
  } catch (error) {
4992
- if (!isActionValidationError(error)) throw error
5021
+ if (isActionRefusal(error)) {
5022
+ if (error.data !== undefined) {
5023
+ console.error(
5024
+ '[rsc-kit] ' + actionId + ' refused with data, and is a plain function with no .refusal(schema) to check it - the message was sent, the data was not: ' + error.message,
5025
+ )
5026
+ }
4993
5027
 
4994
- result = { validationErrors: error.errors }
5028
+ result = { serverError: error.message }
5029
+ } else if (isActionValidationError(error)) {
5030
+ result = { validationErrors: error.errors }
5031
+ } else {
5032
+ throw error
5033
+ }
4995
5034
  }
4996
5035
 
4997
5036
  // Read after the action has run: what it invalidated is only known once its
@@ -5702,21 +5741,36 @@ function wantsPage(request: Request): boolean {
5702
5741
  return (request.headers.get('Accept') ?? '').includes('text/html')
5703
5742
  }
5704
5743
 
5744
+ /**
5745
+ * The not-found.tsx that answers: the nearest above the route that said
5746
+ * notFound(), else - for a url no route owns - the one at the root, else none.
5747
+ */
5748
+ function nearestNotFound(request: Request): string | null {
5749
+ const route = notFoundRouteOf(request)
5750
+ const chain = route === undefined ? [] : notFoundChain(route)
5751
+
5752
+ return chain[chain.length - 1] ?? ${JSON.stringify(rootNotFound)}
5753
+ }
5754
+
5755
+ const NOT_FOUND_LAYOUTS: Record<string, string[]> = ${JSON.stringify(notFoundLayouts)}
5756
+
5705
5757
  async function renderNotFound(request: Request): Promise<Response> {
5706
- ${notFoundComponent
5758
+ const target = nearestNotFound(request)
5759
+ ${notFoundFiles.length
5707
5760
  ? `
5708
5761
  // A navigation, not a document: answer with the not-found tree as a
5709
5762
  // payload, at the depth the client already holds, so the router renders
5710
5763
  // it in place - the layout stays, the url changes, nothing reloads. The
5711
5764
  // status is still 404; a payload is a payload whatever it says.
5712
- if (request.headers.get('X-RSC')) {
5765
+ if (target && request.headers.get('X-RSC')) {
5713
5766
  try {
5714
- const chain = ${JSON.stringify(notFoundLayouts)}
5767
+ const layouts = NOT_FOUND_LAYOUTS[target] ?? []
5768
+ const chain = layouts
5715
5769
  const from = sharedDepth(request.headers.get('X-RSC-Segments'), chain)
5716
5770
  const { rscPayload } = await handleRscPayload(
5717
- ${JSON.stringify(notFoundComponent)},
5771
+ target,
5718
5772
  {},
5719
- ${JSON.stringify(notFoundLayouts.map((component) => ({ component, props: {} })))},
5773
+ layouts.map((component) => ({ component, props: {} })),
5720
5774
  [],
5721
5775
  {},
5722
5776
  from,
@@ -5739,10 +5793,12 @@ async function renderNotFound(request: Request): Promise<Response> {
5739
5793
  }
5740
5794
 
5741
5795
  try {
5796
+ if (!target) throw new Error('no not-found.tsx')
5797
+
5742
5798
  const { htmlStream } = await handleRscHtmlStream(
5743
- ${JSON.stringify(notFoundComponent)},
5799
+ target,
5744
5800
  {},
5745
- ${JSON.stringify(notFoundLayouts.map((component) => ({ component, props: {} })))},
5801
+ (NOT_FOUND_LAYOUTS[target] ?? []).map((component) => ({ component, props: {} })),
5746
5802
  [],
5747
5803
  {},
5748
5804
  {},