Skip to content

Commit d6195ec

Browse files
sokraijjk
authored andcommitted
Fix layout segment optimization: move app-page imports to server-utility transition (#91701)
## Summary - Add `'turbopack-transition': 'next-server-utility'` to all server-side imports in the `app-page.ts` template so they are properly placed in the server utility layer, rather than relying on `SharedMerged` chunk groups to handle them - Remove the now-unnecessary `next-server-utility` transition from `fillMetadataSegment` import in `app_page_loader_tree.rs` - Add `SharedMultiple` chunk group variant to support multiple shared entries without a merge tag - This avoid passing the `parent` into `SharedMerged`, which did break the layout segment optimization. - Refactor `app_module_graphs` to combine `client_shared_entries` + `has_layout_segments` into a single `Option` parameter, simplifying the API and removing the dead code path for non-page endpoints ## Test plan - [ ] Verify dev and production builds work with app pages that have layout segments - [ ] Verify route handlers still build correctly (no client runtime entries passed) - [ ] Run existing app-dir e2e tests
1 parent 6cb97d6 commit d6195ec

4 files changed

Lines changed: 96 additions & 68 deletions

File tree

crates/next-api/src/app.rs

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -874,52 +874,55 @@ impl AppProject {
874874
&self,
875875
endpoint: Vc<AppEndpoint>,
876876
rsc_entry: ResolvedVc<Box<dyn Module>>,
877-
client_shared_entries: Vc<EvaluatableAssets>,
878-
has_layout_segments: bool,
877+
client_shared_entries_when_has_layout_segments: Option<Vc<EvaluatableAssets>>,
879878
) -> Result<Vc<BaseAndFullModuleGraph>> {
880879
if *self.project.per_page_module_graph().await? {
881880
let next_mode = self.project.next_mode();
882881
let next_mode_ref = next_mode.await?;
883882
let should_trace = next_mode_ref.is_production();
884883
let should_read_binding_usage = next_mode_ref.is_production();
885884

886-
let client_shared_entries = client_shared_entries
887-
.await?
888-
.into_iter()
889-
.map(|m| ResolvedVc::upcast(*m))
890-
.collect();
891885
// Implements layout segment optimization to compute a graph "chain" for each layout
892886
// segment
893887
async move {
894888
let rsc_entry_chunk_group = ChunkGroupEntry::Entry(vec![rsc_entry]);
895889

896890
let mut graphs = vec![];
897-
let mut visited_modules = if has_layout_segments {
891+
let mut visited_modules = VisitedModules::empty();
892+
893+
if let Some(client_shared_entries) = client_shared_entries_when_has_layout_segments
894+
{
898895
let ServerEntries {
899-
server_utils,
900896
server_component_entries,
897+
server_utils,
901898
} = &*find_server_entries(*rsc_entry, should_trace, should_read_binding_usage)
902899
.await?;
903900

901+
let client_shared_entries = client_shared_entries
902+
.await?
903+
.into_iter()
904+
.map(|m| ResolvedVc::upcast(*m))
905+
.collect();
906+
907+
// SEGMENT: client_shared_entries and server utils shared by the layout segments
908+
// and the page
904909
let graph = SingleModuleGraph::new_with_entries_visited_intern(
905910
vec![
906-
ChunkGroupEntry::SharedMerged {
907-
parent: Box::new(rsc_entry_chunk_group.clone()),
908-
merge_tag: NEXT_SERVER_UTILITY_MERGE_TAG.clone(),
909-
entries: server_utils
911+
ChunkGroupEntry::Entry(client_shared_entries),
912+
ChunkGroupEntry::SharedMultiple(
913+
server_utils
910914
.iter()
911915
.map(async |m| Ok(ResolvedVc::upcast(m.await?.module)))
912916
.try_join()
913917
.await?,
914-
},
915-
ChunkGroupEntry::Entry(client_shared_entries),
918+
),
916919
],
917-
VisitedModules::empty(),
920+
visited_modules,
918921
should_trace,
919922
should_read_binding_usage,
920923
);
921924
graphs.push(graph);
922-
let mut visited_modules = VisitedModules::from_graph(graph);
925+
visited_modules = VisitedModules::concatenate(visited_modules, graph);
923926

924927
// Skip the last server component, which is the page itself, because that one
925928
// won't have it's visited modules added, and will be visited in the next step
@@ -928,6 +931,7 @@ impl AppProject {
928931
.iter()
929932
.take(server_component_entries.len().saturating_sub(1))
930933
{
934+
// SEGMENT: layout segment
931935
let graph = SingleModuleGraph::new_with_entries_visited_intern(
932936
vec![ChunkGroupEntry::Shared(ResolvedVc::upcast(*module))],
933937
visited_modules,
@@ -948,18 +952,9 @@ impl AppProject {
948952
VisitedModules::with_incremented_index(visited_modules)
949953
};
950954
}
951-
visited_modules
952-
} else {
953-
let graph = SingleModuleGraph::new_with_entries_visited_intern(
954-
vec![ChunkGroupEntry::Entry(client_shared_entries)],
955-
VisitedModules::empty(),
956-
should_trace,
957-
should_read_binding_usage,
958-
);
959-
graphs.push(graph);
960-
VisitedModules::from_graph(graph)
961-
};
955+
}
962956

957+
// SEGMENT: rsc entry chunk group
963958
let graph = SingleModuleGraph::new_with_entries_visited_intern(
964959
vec![rsc_entry_chunk_group],
965960
visited_modules,
@@ -1254,12 +1249,7 @@ impl AppEndpoint {
12541249
self,
12551250
*rsc_entry,
12561251
// We only need the client runtime entries for pages not for Route Handlers
1257-
if is_app_page {
1258-
this.app_project.client_runtime_entries()
1259-
} else {
1260-
EvaluatableAssets::empty()
1261-
},
1262-
is_app_page,
1252+
is_app_page.then(|| this.app_project.client_runtime_entries()),
12631253
)
12641254
.await?;
12651255

@@ -2133,13 +2123,14 @@ impl Endpoint for AppEndpoint {
21332123
async fn module_graphs(self: Vc<Self>) -> Result<Vc<ModuleGraphs>> {
21342124
let this = self.await?;
21352125
let app_entry = self.app_endpoint_entry().await?;
2126+
let is_app_page = matches!(this.ty, AppEndpointType::Page { .. });
21362127
let module_graphs = this
21372128
.app_project
21382129
.app_module_graphs(
21392130
self,
21402131
*app_entry.rsc_entry,
2141-
this.app_project.client_runtime_entries(),
2142-
matches!(this.ty, AppEndpointType::Page { .. }),
2132+
// We only need the client runtime entries for pages not for Route Handlers
2133+
is_app_page.then(|| this.app_project.client_runtime_entries()),
21432134
)
21442135
.await?;
21452136
Ok(Vc::cell(vec![module_graphs.full]))

crates/next-core/src/app_page_loader_tree.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,7 @@ impl AppPageLoaderTreeBuilder {
230230
let identifier = magic_identifier::mangle(&format!("{name} #{i}"));
231231
let inner_module_id = format!("METADATA_{i}");
232232
let helper_import = rcstr!(
233-
"import { fillMetadataSegment } from 'next/dist/lib/metadata/get-metadata-route' with \
234-
{ 'turbopack-transition': 'next-server-utility' }"
233+
"import { fillMetadataSegment } from 'next/dist/lib/metadata/get-metadata-route'"
235234
);
236235

237236
if !self.base.imports.contains(&helper_import) {

packages/next/src/build/templates/app-page.ts

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,38 @@ import {
99

1010
import { RouteKind } from '../../server/route-kind' with { 'turbopack-transition': 'next-server-utility' }
1111

12-
import { getRevalidateReason } from '../../server/instrumentation/utils'
13-
import { getTracer, SpanKind, type Span } from '../../server/lib/trace/tracer'
12+
import { getRevalidateReason } from '../../server/instrumentation/utils' with { 'turbopack-transition': 'next-server-utility' }
13+
import {
14+
getTracer,
15+
SpanKind,
16+
type Span,
17+
} from '../../server/lib/trace/tracer' with { 'turbopack-transition': 'next-server-utility' }
1418
import type { RequestMeta } from '../../server/request-meta'
1519
import {
1620
addRequestMeta,
1721
getRequestMeta,
1822
setRequestMeta,
19-
} from '../../server/request-meta'
20-
import { BaseServerSpan } from '../../server/lib/trace/constants'
21-
import { interopDefault } from '../../server/app-render/interop-default'
22-
import { stripFlightHeaders } from '../../server/app-render/strip-flight-headers'
23-
import { NodeNextRequest, NodeNextResponse } from '../../server/base-http/node'
24-
import { checkIsAppPPREnabled } from '../../server/lib/experimental/ppr'
23+
} from '../../server/request-meta' with { 'turbopack-transition': 'next-server-utility' }
24+
import { BaseServerSpan } from '../../server/lib/trace/constants' with { 'turbopack-transition': 'next-server-utility' }
25+
import { interopDefault } from '../../server/app-render/interop-default' with { 'turbopack-transition': 'next-server-utility' }
26+
import { stripFlightHeaders } from '../../server/app-render/strip-flight-headers' with { 'turbopack-transition': 'next-server-utility' }
27+
import {
28+
NodeNextRequest,
29+
NodeNextResponse,
30+
} from '../../server/base-http/node' with { 'turbopack-transition': 'next-server-utility' }
31+
import { checkIsAppPPREnabled } from '../../server/lib/experimental/ppr' with { 'turbopack-transition': 'next-server-utility' }
2532
import {
2633
getFallbackRouteParams,
2734
createOpaqueFallbackRouteParams,
2835
type OpaqueFallbackRouteParams,
29-
} from '../../server/request/fallback-params'
30-
import { setManifestsSingleton } from '../../server/app-render/manifests-singleton'
36+
} from '../../server/request/fallback-params' with { 'turbopack-transition': 'next-server-utility' }
37+
import { setManifestsSingleton } from '../../server/app-render/manifests-singleton' with { 'turbopack-transition': 'next-server-utility' }
3138
import {
3239
isHtmlBotRequest,
3340
shouldServeStreamingMetadata,
34-
} from '../../server/lib/streaming-metadata'
35-
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
36-
import { getIsPossibleServerAction } from '../../server/lib/server-action-request-meta'
41+
} from '../../server/lib/streaming-metadata' with { 'turbopack-transition': 'next-server-utility' }
42+
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths' with { 'turbopack-transition': 'next-server-utility' }
43+
import { getIsPossibleServerAction } from '../../server/lib/server-action-request-meta' with { 'turbopack-transition': 'next-server-utility' }
3744
import {
3845
RSC_HEADER,
3946
NEXT_ROUTER_PREFETCH_HEADER,
@@ -42,37 +49,43 @@ import {
4249
NEXT_IS_PRERENDER_HEADER,
4350
NEXT_DID_POSTPONE_HEADER,
4451
RSC_CONTENT_TYPE_HEADER,
45-
} from '../../client/components/app-router-headers'
46-
import { getBotType, isBot } from '../../shared/lib/router/utils/is-bot'
52+
} from '../../client/components/app-router-headers' with { 'turbopack-transition': 'next-server-utility' }
53+
import {
54+
getBotType,
55+
isBot,
56+
} from '../../shared/lib/router/utils/is-bot' with { 'turbopack-transition': 'next-server-utility' }
4757
import {
4858
CachedRouteKind,
4959
IncrementalCacheKind,
5060
type CachedAppPageValue,
5161
type CachedPageValue,
5262
type ResponseCacheEntry,
5363
type ResponseGenerator,
54-
} from '../../server/response-cache'
55-
import { FallbackMode, parseFallbackField } from '../../lib/fallback'
56-
import RenderResult from '../../server/render-result'
64+
} from '../../server/response-cache' with { 'turbopack-transition': 'next-server-utility' }
65+
import {
66+
FallbackMode,
67+
parseFallbackField,
68+
} from '../../lib/fallback' with { 'turbopack-transition': 'next-server-utility' }
69+
import RenderResult from '../../server/render-result' with { 'turbopack-transition': 'next-server-utility' }
5770
import {
5871
CACHE_ONE_YEAR_SECONDS,
5972
HTML_CONTENT_TYPE_HEADER,
6073
NEXT_CACHE_TAGS_HEADER,
6174
NEXT_NAV_DEPLOYMENT_ID_HEADER,
6275
NEXT_RESUME_HEADER,
6376
NEXT_RESUME_STATE_LENGTH_HEADER,
64-
} from '../../lib/constants'
77+
} from '../../lib/constants' with { 'turbopack-transition': 'next-server-utility' }
6578
import type { CacheControl } from '../../server/lib/cache-control'
66-
import { ENCODED_TAGS } from '../../server/stream-utils/encoded-tags'
67-
import { createInstantTestScriptInsertionTransformStream } from '../../server/stream-utils/node-web-streams-helper'
68-
import { sendRenderResult } from '../../server/send-payload'
69-
import { NoFallbackError } from '../../shared/lib/no-fallback-error.external'
70-
import { parseMaxPostponedStateSize } from '../../shared/lib/size-limit'
79+
import { ENCODED_TAGS } from '../../server/stream-utils/encoded-tags' with { 'turbopack-transition': 'next-server-utility' }
80+
import { createInstantTestScriptInsertionTransformStream } from '../../server/stream-utils/node-web-streams-helper' with { 'turbopack-transition': 'next-server-utility' }
81+
import { sendRenderResult } from '../../server/send-payload' with { 'turbopack-transition': 'next-server-utility' }
82+
import { NoFallbackError } from '../../shared/lib/no-fallback-error.external' with { 'turbopack-transition': 'next-server-utility' }
83+
import { parseMaxPostponedStateSize } from '../../shared/lib/size-limit' with { 'turbopack-transition': 'next-server-utility' }
7184
import {
7285
getMaxPostponedStateSize,
7386
getPostponedStateExceededErrorMessage,
7487
readBodyWithSizeLimit,
75-
} from '../../server/lib/postponed-request-body'
88+
} from '../../server/lib/postponed-request-body' with { 'turbopack-transition': 'next-server-utility' }
7689

7790
// These are injected by the loader afterwards.
7891

@@ -98,14 +111,14 @@ export const __next_app__ = {
98111
}
99112

100113
import * as entryBase from '../../server/app-render/entry-base' with { 'turbopack-transition': 'next-server-utility' }
101-
import { RedirectStatusCode } from '../../client/components/redirect-status-code'
102-
import { InvariantError } from '../../shared/lib/invariant-error'
103-
import { scheduleOnNextTick } from '../../lib/scheduler'
104-
import { isInterceptionRouteAppPath } from '../../shared/lib/router/utils/interception-routes'
114+
import { RedirectStatusCode } from '../../client/components/redirect-status-code' with { 'turbopack-transition': 'next-server-utility' }
115+
import { InvariantError } from '../../shared/lib/invariant-error' with { 'turbopack-transition': 'next-server-utility' }
116+
import { scheduleOnNextTick } from '../../lib/scheduler' with { 'turbopack-transition': 'next-server-utility' }
117+
import { isInterceptionRouteAppPath } from '../../shared/lib/router/utils/interception-routes' with { 'turbopack-transition': 'next-server-utility' }
105118
import {
106119
getParamProperties,
107120
getSegmentParam,
108-
} from '../../shared/lib/router/utils/get-segment-param'
121+
} from '../../shared/lib/router/utils/get-segment-param' with { 'turbopack-transition': 'next-server-utility' }
109122

110123
export * from '../../server/app-render/entry-base' with { 'turbopack-transition': 'next-server-utility' }
111124

turbopack/crates/turbopack-core/src/module_graph/chunk_group_info.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ pub enum ChunkGroupEntry {
136136
entries: Vec<ResolvedVc<Box<dyn Module>>>,
137137
},
138138
Shared(ResolvedVc<Box<dyn Module>>),
139+
SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
139140
SharedMerged {
140141
parent: Box<ChunkGroupEntry>,
141142
merge_tag: RcStr,
@@ -150,6 +151,7 @@ impl ChunkGroupEntry {
150151
}
151152
Self::Entry(entries)
152153
| Self::IsolatedMerged { entries, .. }
154+
| Self::SharedMultiple(entries)
153155
| Self::SharedMerged { entries, .. } => Either::Right(entries.iter().copied()),
154156
}
155157
}
@@ -175,6 +177,8 @@ pub enum ChunkGroup {
175177
/// A shared chunk group. Corresponds to an incoming [ChunkingType::Shared] reference with
176178
/// `merge_tag: None`
177179
Shared(ResolvedVc<Box<dyn Module>>),
180+
/// A shared chunk group with multiple entries.
181+
SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
178182
/// A shared chunk group. Corresponds to an incoming [ChunkingType::Shared] reference with
179183
/// `merge_tag: Some(_)`
180184
SharedMerged {
@@ -205,6 +209,7 @@ impl ChunkGroup {
205209
}
206210
ChunkGroup::Entry(entries)
207211
| ChunkGroup::IsolatedMerged { entries, .. }
212+
| ChunkGroup::SharedMultiple(entries)
208213
| ChunkGroup::SharedMerged { entries, .. } => Either::Right(entries.iter().copied()),
209214
}
210215
}
@@ -214,6 +219,7 @@ impl ChunkGroup {
214219
ChunkGroup::Async(_) | ChunkGroup::Isolated(_) | ChunkGroup::Shared(_) => 1,
215220
ChunkGroup::Entry(entries)
216221
| ChunkGroup::IsolatedMerged { entries, .. }
222+
| ChunkGroup::SharedMultiple(entries)
217223
| ChunkGroup::SharedMerged { entries, .. } => entries.len(),
218224
}
219225
}
@@ -237,6 +243,14 @@ impl ChunkGroup {
237243
ChunkGroup::Shared(entry) => turbofmt!("ChunkGroup::Shared({:?})", entry.ident())
238244
.await?
239245
.to_string(),
246+
ChunkGroup::SharedMultiple(entries) => format!(
247+
"ChunkGroup::SharedMultiple({:?})",
248+
entries
249+
.iter()
250+
.map(|m| m.ident().to_string())
251+
.try_join()
252+
.await?
253+
),
240254
ChunkGroup::IsolatedMerged {
241255
parent,
242256
merge_tag,
@@ -286,6 +300,7 @@ pub enum ChunkGroupKey {
286300
merge_tag: RcStr,
287301
},
288302
Shared(ResolvedVc<Box<dyn Module>>),
303+
SharedMultiple(Vec<ResolvedVc<Box<dyn Module>>>),
289304
SharedMerged {
290305
parent: ChunkGroupId,
291306
merge_tag: RcStr,
@@ -322,6 +337,14 @@ impl ChunkGroupKey {
322337
ChunkGroupKey::Shared(module) => {
323338
turbofmt!("Shared({:?})", module.ident()).await?.to_string()
324339
}
340+
ChunkGroupKey::SharedMultiple(entries) => format!(
341+
"SharedMultiple({:?})",
342+
entries
343+
.iter()
344+
.map(|m| m.ident().to_string())
345+
.try_join()
346+
.await?
347+
),
325348
ChunkGroupKey::SharedMerged { parent, merge_tag } => {
326349
format!(
327350
"SharedMerged {{ parent: {}, merge_tag: {:?} }}",
@@ -446,6 +469,7 @@ pub async fn compute_chunk_group_info(graph: &ModuleGraph) -> Result<Vc<ChunkGro
446469
ChunkGroupEntry::Async(entry) => ChunkGroupKey::Async(entry),
447470
ChunkGroupEntry::Isolated(entry) => ChunkGroupKey::Isolated(entry),
448471
ChunkGroupEntry::Shared(entry) => ChunkGroupKey::Shared(entry),
472+
ChunkGroupEntry::SharedMultiple(entries) => ChunkGroupKey::SharedMultiple(entries),
449473
ChunkGroupEntry::IsolatedMerged {
450474
parent,
451475
merge_tag,
@@ -752,6 +776,7 @@ pub async fn compute_chunk_group_info(graph: &ModuleGraph) -> Result<Vc<ChunkGro
752776
}
753777
}
754778
ChunkGroupKey::Shared(module) => ChunkGroup::Shared(module),
779+
ChunkGroupKey::SharedMultiple(entries) => ChunkGroup::SharedMultiple(entries),
755780
ChunkGroupKey::SharedMerged { parent, merge_tag } => ChunkGroup::SharedMerged {
756781
parent: parent.0 as usize,
757782
merge_tag,

0 commit comments

Comments
 (0)