Skip to content

Commit dbda43d

Browse files
Merge branch 'canary' into appdir-crossorigin
2 parents a344c2d + e970e05 commit dbda43d

File tree

167 files changed

+6238
-5327
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

167 files changed

+6238
-5327
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
# Tooling & Telemetry
3030

31-
/packages/next/src/build/ @timneutkens @ijjk @shuding @huozhi @ztanner @vercel/web-tooling
31+
/packages/next/src/build/ @timneutkens @ijjk @shuding @huozhi @ztanner @feedthejim @vercel/web-tooling
3232
/packages/next/src/server/lib/router-utils/setup-dev.ts @timneutkens @ijjk @shuding @huozhi @feedthejim @ztanner @wyattjoh @vercel/web-tooling
3333
/packages/next/src/telemetry/ @timneutkens @ijjk @shuding @padmaia
3434
/packages/next-swc/ @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/02-app/02-api-reference/01-components/image.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ The cache status of an image can be determined by reading the value of the `x-ne
576576
The expiration (or rather Max Age) is defined by either the [`minimumCacheTTL`](#minimumcachettl) configuration or the upstream image `Cache-Control` header, whichever is larger. Specifically, the `max-age` value of the `Cache-Control` header is used. If both `s-maxage` and `max-age` are found, then `s-maxage` is preferred. The `max-age` is also passed-through to any downstream clients including CDNs and browsers.
577577

578578
- You can configure [`minimumCacheTTL`](#minimumcachettl) to increase the cache duration when the upstream image does not include `Cache-Control` header or the value is very low.
579-
- You can configure [`deviceSizes`](#devicesizes) and [`imageSizes`](#devicesizes) to reduce the total number of possible generated images.
579+
- You can configure [`deviceSizes`](#devicesizes) and [`imageSizes`](#imagesizes) to reduce the total number of possible generated images.
580580
- You can configure [formats](#formats) to disable multiple formats in favor of a single image format.
581581

582582
### `minimumCacheTTL`
@@ -599,7 +599,7 @@ There is no mechanism to invalidate the cache at this time, so its best to keep
599599

600600
### `disableStaticImages`
601601

602-
The default behavior allows you to import static files such as `import icon from './icon.png` and then pass that to the `src` property.
602+
The default behavior allows you to import static files such as `import icon from './icon.png'` and then pass that to the `src` property.
603603

604604
In some cases, you may wish to disable this feature if it conflicts with other plugins that expect the import to behave differently.
605605

docs/03-pages/02-api-reference/01-components/image-legacy.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ The cache status of an image can be determined by reading the value of the `x-ne
519519
The expiration (or rather Max Age) is defined by either the [`minimumCacheTTL`](#minimum-cache-ttl) configuration or the upstream image `Cache-Control` header, whichever is larger. Specifically, the `max-age` value of the `Cache-Control` header is used. If both `s-maxage` and `max-age` are found, then `s-maxage` is preferred. The `max-age` is also passed-through to any downstream clients including CDNs and browsers.
520520

521521
- You can configure [`minimumCacheTTL`](#minimum-cache-ttl) to increase the cache duration when the upstream image does not include `Cache-Control` header or the value is very low.
522-
- You can configure [`deviceSizes`](#device-sizes) and [`imageSizes`](#device-sizes) to reduce the total number of possible generated images.
522+
- You can configure [`deviceSizes`](#device-sizes) and [`imageSizes`](#image-sizes) to reduce the total number of possible generated images.
523523
- You can configure [formats](#acceptable-formats) to disable multiple formats in favor of a single image format.
524524

525525
### Minimum Cache TTL
@@ -542,7 +542,7 @@ There is no mechanism to invalidate the cache at this time, so its best to keep
542542

543543
### Disable Static Imports
544544

545-
The default behavior allows you to import static files such as `import icon from './icon.png` and then pass that to the `src` property.
545+
The default behavior allows you to import static files such as `import icon from './icon.png'` and then pass that to the `src` property.
546546

547547
In some cases, you may wish to disable this feature if it conflicts with other plugins that expect the import to behave differently.
548548

examples/with-stripe-typescript/app/api/webhooks/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export async function POST(req: Request) {
5151
console.log(`💰 PaymentIntent status: ${data.status}`)
5252
break
5353
default:
54-
throw new Error(`Unhhandled event: ${event.type}`)
54+
throw new Error(`Unhandled event: ${event.type}`)
5555
}
5656
} catch (error) {
5757
console.log(error)

packages/next/src/build/handle-externals.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,8 @@ export function makeExternalHandler({
301301
return resolveResult.localRes
302302
}
303303

304-
// Forcedly resolve the styled-jsx installed by next.js,
305-
// since `resolveExternal` cannot find the styled-jsx dep with pnpm
306-
if (request === 'styled-jsx/style') {
307-
resolveResult.res = defaultOverrides['styled-jsx/style']
304+
if (request === 'styled-jsx/style' && !isAppLayer) {
305+
return `commonjs ${defaultOverrides['styled-jsx/style']}`
308306
}
309307

310308
const { res, isEsm } = resolveResult

packages/next/src/build/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
129129
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
130130
import {
131131
ACTION,
132+
NEXT_ROUTER_PREFETCH,
132133
RSC,
133134
RSC_CONTENT_TYPE_HEADER,
134135
RSC_VARY_HEADER,
@@ -227,6 +228,7 @@ export type RoutesManifest = {
227228
rsc: {
228229
header: typeof RSC
229230
varyHeader: typeof RSC_VARY_HEADER
231+
prefetchHeader: typeof NEXT_ROUTER_PREFETCH
230232
}
231233
skipMiddlewareUrlNormalize?: boolean
232234
caseSensitive?: boolean
@@ -795,6 +797,7 @@ export default async function build(
795797
rsc: {
796798
header: RSC,
797799
varyHeader: RSC_VARY_HEADER,
800+
prefetchHeader: NEXT_ROUTER_PREFETCH,
798801
contentTypeHeader: RSC_CONTENT_TYPE_HEADER,
799802
},
800803
skipMiddlewareUrlNormalize: config.skipMiddlewareUrlNormalize,
@@ -1055,6 +1058,7 @@ export default async function build(
10551058
const additionalSsgPaths = new Map<string, Array<string>>()
10561059
const additionalSsgPathsEncoded = new Map<string, Array<string>>()
10571060
const appStaticPaths = new Map<string, Array<string>>()
1061+
const appPrefetchPaths = new Map<string, string>()
10581062
const appStaticPathsEncoded = new Map<string, Array<string>>()
10591063
const appNormalizedPaths = new Map<string, string>()
10601064
const appDynamicParamPaths = new Set<string>()
@@ -1554,6 +1558,14 @@ export default async function build(
15541558
appDynamicParamPaths.add(originalAppPath)
15551559
}
15561560
appDefaultConfigs.set(originalAppPath, appConfig)
1561+
1562+
if (
1563+
!isStatic &&
1564+
!isAppRouteRoute(originalAppPath) &&
1565+
!isDynamicRoute(originalAppPath)
1566+
) {
1567+
appPrefetchPaths.set(originalAppPath, page)
1568+
}
15571569
}
15581570
} else {
15591571
if (isEdgeRuntime(pageRuntime)) {
@@ -2001,6 +2013,15 @@ export default async function build(
20012013
})
20022014
})
20032015

2016+
for (const [originalAppPath, page] of appPrefetchPaths) {
2017+
defaultMap[page] = {
2018+
page: originalAppPath,
2019+
query: {},
2020+
_isAppDir: true,
2021+
_isAppPrefetch: true,
2022+
}
2023+
}
2024+
20042025
if (i18n) {
20052026
for (const page of [
20062027
...staticPages,

packages/next/src/client/components/router-reducer/create-router-cache-key.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export function createRouterCacheKey(
55
withoutSearchParameters: boolean = false
66
) {
77
return Array.isArray(segment)
8-
? `${segment[0]}|${segment[1]}|${segment[2]}`
8+
? `${segment[0]}|${segment[1]}|${segment[2]}`.toLowerCase()
99
: withoutSearchParameters && segment.startsWith('__PAGE__')
1010
? '__PAGE__'
1111
: segment

packages/next/src/client/components/static-generation-async-storage.external.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface StaticGenerationStore {
3030
dynamicUsageDescription?: string
3131
dynamicUsageStack?: string
3232
dynamicUsageErr?: DynamicServerError
33+
staticPrefetchBailout?: boolean
3334

3435
nextFetchId?: number
3536
pathWasRevalidated?: boolean

packages/next/src/client/components/static-generation-bailout.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ export const staticGenerationBailout: StaticGenerationBailout = (
3838

3939
if (staticGenerationStore) {
4040
staticGenerationStore.revalidate = 0
41+
42+
if (!opts?.dynamic) {
43+
// we can statically prefetch pages that opt into dynamic,
44+
// but not things like headers/cookies
45+
staticGenerationStore.staticPrefetchBailout = true
46+
}
4147
}
4248

4349
if (staticGenerationStore?.isStaticGeneration) {

packages/next/src/export/routes/app-page.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import type { NextParsedUrlQuery } from '../../server/request-meta'
66

77
import fs from 'fs/promises'
88
import { MockedRequest, MockedResponse } from '../../server/lib/mock-request'
9+
import {
10+
RSC,
11+
NEXT_URL,
12+
NEXT_ROUTER_PREFETCH,
13+
} from '../../client/components/app-router-headers'
914
import { isDynamicUsageError } from '../helpers/is-dynamic-usage-error'
1015
import { NEXT_CACHE_TAGS_HEADER } from '../../lib/constants'
1116
import { hasNextSupport } from '../../telemetry/ci-info'
@@ -19,6 +24,37 @@ const render: AppPageRender = (...args) => {
1924
)
2025
}
2126

27+
export async function generatePrefetchRsc(
28+
req: MockedRequest,
29+
path: string,
30+
res: MockedResponse,
31+
pathname: string,
32+
htmlFilepath: string,
33+
renderOpts: RenderOpts
34+
) {
35+
req.headers[RSC.toLowerCase()] = '1'
36+
req.headers[NEXT_URL.toLowerCase()] = path
37+
req.headers[NEXT_ROUTER_PREFETCH.toLowerCase()] = '1'
38+
39+
renderOpts.supportsDynamicHTML = true
40+
renderOpts.isPrefetch = true
41+
delete renderOpts.isRevalidate
42+
43+
const prefetchRenderResult = await render(req, res, pathname, {}, renderOpts)
44+
45+
prefetchRenderResult.pipe(res)
46+
await res.hasStreamed
47+
48+
const prefetchRscData = Buffer.concat(res.buffers)
49+
50+
if ((renderOpts as any).store.staticPrefetchBailout) return
51+
52+
await fs.writeFile(
53+
htmlFilepath.replace(/\.html$/, '.prefetch.rsc'),
54+
prefetchRscData
55+
)
56+
}
57+
2258
export async function exportAppPage(
2359
req: MockedRequest,
2460
res: MockedResponse,
@@ -29,14 +65,28 @@ export async function exportAppPage(
2965
renderOpts: RenderOpts,
3066
htmlFilepath: string,
3167
debugOutput: boolean,
32-
isDynamicError: boolean
68+
isDynamicError: boolean,
69+
isAppPrefetch: boolean
3370
): Promise<ExportPageResult> {
3471
// If the page is `/_not-found`, then we should update the page to be `/404`.
3572
if (page === '/_not-found') {
3673
pathname = '/404'
3774
}
3875

3976
try {
77+
if (isAppPrefetch) {
78+
await generatePrefetchRsc(
79+
req,
80+
path,
81+
res,
82+
pathname,
83+
htmlFilepath,
84+
renderOpts
85+
)
86+
87+
return { fromBuildExportRevalidate: 0 }
88+
}
89+
4090
const result = await render(req, res, pathname, query, renderOpts)
4191
const html = result.toUnchunkedString()
4292
const { metadata } = result
@@ -50,6 +100,17 @@ export async function exportAppPage(
50100
)
51101
}
52102

103+
if (!(renderOpts as any).store.staticPrefetchBailout) {
104+
await generatePrefetchRsc(
105+
req,
106+
path,
107+
res,
108+
pathname,
109+
htmlFilepath,
110+
renderOpts
111+
)
112+
}
113+
53114
const { staticBailoutInfo = {} } = metadata
54115

55116
if (revalidate === 0 && debugOutput && staticBailoutInfo?.description) {

packages/next/src/export/worker.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ async function exportPageImpl(input: ExportPageInput) {
108108
// Check if this is an `app/` page.
109109
_isAppDir: isAppDir = false,
110110

111+
// Check if this is an `app/` prefix request.
112+
_isAppPrefetch: isAppPrefetch = false,
113+
111114
// Check if this should error when dynamic usage is detected.
112115
_isDynamicError: isDynamicError = false,
113116

@@ -306,7 +309,8 @@ async function exportPageImpl(input: ExportPageInput) {
306309
renderOpts,
307310
htmlFilepath,
308311
debugOutput,
309-
isDynamicError
312+
isDynamicError,
313+
isAppPrefetch
310314
)
311315
}
312316

packages/next/src/lib/turbopack-warning.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ const supportedTurbopackNextConfigOptions = [
6464
'experimental.deploymentId',
6565

6666
// Experimental options that don't affect compilation
67+
'serverRuntimeConfig',
68+
'publicRuntimeConfig',
6769
'experimental.proxyTimeout',
6870
'experimental.caseSensitiveRoutes',
6971
'experimental.workerThreads',

packages/next/src/server/app-render/app-render.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,6 +1108,13 @@ export const renderToHTMLOrFlight: AppPageRender = (
11081108
// Explicit refresh
11091109
flightRouterState[3] === 'refetch'
11101110

1111+
const shouldSkipComponentTree =
1112+
isPrefetch &&
1113+
!Boolean(components.loading) &&
1114+
(flightRouterState ||
1115+
// If there is no flightRouterState, we need to check the entire loader tree, as otherwise we'll be only checking the root
1116+
!hasLoadingComponentInTree(loaderTree))
1117+
11111118
if (!parentRendered && renderComponentsOnThisLevel) {
11121119
const overriddenSegment =
11131120
flightRouterState &&
@@ -1124,9 +1131,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
11241131
getDynamicParamFromSegment,
11251132
query
11261133
),
1127-
isPrefetch &&
1128-
!Boolean(components.loading) &&
1129-
!hasLoadingComponentInTree(loaderTree)
1134+
shouldSkipComponentTree
11301135
? null
11311136
: // Create component tree using the slice of the loaderTree
11321137
// @ts-expect-error TODO-APP: fix async component type
@@ -1149,9 +1154,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
11491154

11501155
return <Component />
11511156
}),
1152-
isPrefetch &&
1153-
!Boolean(components.loading) &&
1154-
!hasLoadingComponentInTree(loaderTree)
1157+
shouldSkipComponentTree
11551158
? null
11561159
: (() => {
11571160
const { layoutOrPagePath } =

packages/next/src/server/app-render/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export interface RenderOptsPartial {
132132
) => Promise<NextConfigComplete>
133133
serverActionsBodySizeLimit?: SizeLimit
134134
params?: ParsedUrlQuery
135+
isPrefetch?: boolean
135136
}
136137

137138
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial

packages/next/src/server/async-storage/static-generation-async-storage-wrapper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type StaticGenerationContext = {
1212
isRevalidate?: boolean
1313
isOnDemandRevalidate?: boolean
1414
isBot?: boolean
15+
isPrefetch?: boolean
1516
nextExport?: boolean
1617
fetchCache?: StaticGenerationStore['fetchCache']
1718
isDraftMode?: boolean

packages/next/src/server/base-server.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ import {
8686
FLIGHT_PARAMETERS,
8787
NEXT_RSC_UNION_QUERY,
8888
ACTION,
89+
NEXT_ROUTER_PREFETCH,
90+
RSC_CONTENT_TYPE_HEADER,
8991
} from '../client/components/app-router-headers'
9092
import {
9193
MatchOptions,
@@ -2124,6 +2126,27 @@ export default abstract class Server<ServerOptions extends Options = Options> {
21242126
} else if (
21252127
components.routeModule?.definition.kind === RouteKind.APP_PAGE
21262128
) {
2129+
const isAppPrefetch = req.headers[NEXT_ROUTER_PREFETCH.toLowerCase()]
2130+
2131+
if (isAppPrefetch && process.env.NODE_ENV === 'production') {
2132+
try {
2133+
const prefetchRsc = await this.getPrefetchRsc(resolvedUrlPathname)
2134+
2135+
if (prefetchRsc) {
2136+
res.setHeader(
2137+
'cache-control',
2138+
'private, no-cache, no-store, max-age=0, must-revalidate'
2139+
)
2140+
res.setHeader('content-type', RSC_CONTENT_TYPE_HEADER)
2141+
res.body(prefetchRsc).send()
2142+
return null
2143+
}
2144+
} catch (_) {
2145+
// we fallback to invoking the function if prefetch
2146+
// data is not available
2147+
}
2148+
}
2149+
21272150
const module = components.routeModule as AppPageRouteModule
21282151

21292152
// Due to the way we pass data by mutating `renderOpts`, we can't extend the

packages/next/src/server/render.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ export type RenderOptsPartial = {
283283
deploymentId?: string
284284
isServerAction?: boolean
285285
isExperimentalCompile?: boolean
286+
isPrefetch?: boolean
286287
}
287288

288289
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial

0 commit comments

Comments
 (0)