Repository: vercel/next.js
Pull request: add unstable_prefetch config to typescript plugin

In #8290 i forgot to update our typescript plugin, so exporting `unstable_prefetch` caused it to error.

This is fixed now:
<img width="871" height="145" alt="Screenshot 2025-08-28 at 17 19 18" src="https://github.com/user-attachments/assets/ca295029-5a93-483b-8f2b-2f821c452397" />
<img width="841" height="176" alt="Screenshot 2025-08-28 at 17 19 29" src="https://github.com/user-attachments/assets/414b18f8-c3c0-46fd-acd0-25b04a85fbd2" />
I've also added some type safety to the `options` objects that we specify there to make sure they're in sync with the values they describe.


Changed files:
filename: packages/next/src/server/typescript/rules/config.ts
patch: @@ -12,6 +12,7 @@ import {
   LEGACY_CONFIG_EXPORT,
 } from '../constant'
 import type tsModule from 'typescript/lib/tsserverlibrary'
+import type { AppSegmentConfig } from '../../../build/segment-config/app/app-segment-config'
 
 const API_DOCS: Record<
   string,
@@ -37,7 +38,7 @@ const API_DOCS: Record<
         'This errors if any dynamic Hooks or fetches are used. (This is equivalent to `getStaticProps`.)',
       '"force-static"':
         'This forces caching of all fetches and returns empty values from `cookies`, `headers` and `useSearchParams`.',
-    },
+    } satisfies DocsOptionsObject<FullAppSegmentConfig['dynamic']>,
     link: 'https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic',
   },
   fetchCache: {
@@ -58,7 +59,7 @@ const API_DOCS: Record<
         "This lets you enforce that all data opts into caching. This option makes `fetch()` reject with an error if `cache: 'force-cache'` is provided. It also changes the default to `force-cache`. This error can be discovered early during static builds - or dynamically during Edge rendering.",
       '"force-cache"':
         "This lets you intentionally opt-in to all caching of data. This option forces all fetches to be cache even if the `cache: 'no-store'` option is passed to `fetch()`.",
-    },
+    } satisfies DocsOptionsObject<FullAppSegmentConfig['fetchCache']>,
     link: 'https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#fetchcache',
   },
   preferredRegion: {
@@ -115,7 +116,7 @@ const API_DOCS: Record<
       true: 'Allow rendering dynamic params that are not generated by `generateStaticParams`.',
       false:
         'Disallow rendering dynamic params that are not generated by `generateStaticParams`.',
-    },
+    } satisfies DocsOptionsObject<FullAppSegmentConfig['dynamicParams']>,
     link: 'https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams',
     isValid: (value: string) => {
       return value === 'true' || value === 'false'
@@ -128,7 +129,9 @@ const API_DOCS: Record<
       '"nodejs"': 'Prefer the Node.js runtime.',
       '"edge"': 'Prefer the Edge runtime.',
       '"experimental-edge"': `@deprecated\n\nThis option is no longer experimental. Use \`edge\` instead.`,
-    },
+    } satisfies DocsOptionsObject<
+      FullAppSegmentConfig['runtime'] | 'experimental-edge'
+    >,
     link: 'https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#runtime',
   },
   metadata: {
@@ -152,13 +155,44 @@ const API_DOCS: Record<
     options: {
       true: 'Enable PPR for this route',
       false: 'Disable PPR for this route',
-    },
+    } satisfies DocsOptionsObject<FullAppSegmentConfig['experimental_ppr']>,
     isValid: (value: string) => {
       return value === 'true' || value === 'false'
     },
   },
+  unstable_prefetch: {
+    description: `Specifies the default prefetching behavior for this segment. This configuration is currently under development and will change.`,
+    link: '(docs coming soon)',
+    options: {
+      '"unstable_static"':
+        'Only static and cached parts of the page will be prefetched. (default)',
+      '"unstable_runtime"':
+        'Parts of the page that use route params, search params, or cookies will also be prefetched.',
+    } satisfies DocsOptionsObject<FullAppSegmentConfig['unstable_prefetch']>,
+  },
 }
 
+type FullAppSegmentConfig = Required<AppSegmentConfig>
+
+/**
+ * Maps a type for a config value to a docs object that lists all its values.
+ * This ensures that all the valid options are listed.
+ * Note that values that aren't primitives are skipped.
+ *
+ * ```
+ * DocsOptionsObject<0 | false | "yes"> = { 0: string, false: string, '"yes"': string }
+ * ```
+ */
+type DocsOptionsObject<T> = {
+  [Value in T as AsObjectKey<Value>]: string
+}
+
+type AsObjectKey<Value> = Value extends string
+  ? `"${Value}"`
+  : Value extends number | boolean | null | undefined
+    ? `${Value}`
+    : never
+
 function visitEntryConfig(
   fileName: string,
   position: number,