mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #466] [MERGED] fix: RSC compatibility for dynamic() and layout segment context #593
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#593
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/466
Author: @benfavre
Created: 3/11/2026
Status: ✅ Merged
Merged: 3/28/2026
Merged by: @james-elicx
Base:
main← Head:fix/rsc-dynamic-layout-segment📝 Commits (8)
e409b2ffix: RSC compatibility for dynamic() and layout segment context3532594Merge remote-tracking branch 'origin/main' into fix/rsc-dynamic-layout-segment54e0793chore: fix formatting and update entry-templates snapshots after merge with maincf42681fix: restore 'use client' in layout-segment-context to fix useSelectedLayoutSegment(s)e296edftest: add regression tests for RSC dynamic() and route handler await paramse2e1d67fix: address bonk review comments on dynamic.ts RSC async patha410275chore: remove unused vi import from dynamic.test.ts5bedf20docs: correct inaccurate React.lazy/RSC comments throughout📊 Changes
10 files changed (+207 additions, -28 deletions)
View changed files
📝
packages/vinext/src/entries/app-rsc-entry.ts(+1 -1)📝
packages/vinext/src/shims/dynamic.ts(+50 -16)📝
packages/vinext/src/shims/layout-segment-context.tsx(+9 -3)📝
tests/__snapshots__/entry-templates.test.ts.snap(+6 -6)📝
tests/dynamic.test.ts(+72 -0)➕
tests/fixtures/app-basic/app/nextjs-compat/dynamic/dynamic-imports/dynamic-rsc.tsx(+11 -0)➕
tests/fixtures/app-basic/app/nextjs-compat/dynamic/rsc-dynamic/page.tsx(+16 -0)➕
tests/fixtures/app-basic/app/nextjs-compat/dynamic/text-dynamic-rsc.tsx(+4 -0)📝
tests/nextjs-compat/app-routes.test.ts(+22 -2)📝
tests/nextjs-compat/dynamic.test.ts(+16 -0)📄 Description
Problem
Two related issues prevent
next/dynamicfrom working correctly in React Server Component environments, plus a missing feature for Next.js 15+ route handlers:1.
dynamic.tscrashes in RSC withReact.lazy is not a functionThe
"use client"directive forcesnext/dynamicinto a client component boundary, butdynamic()should work in server components too. When the RSC entry imports it, thereact-servercondition exports a stripped-down React that does not includeuseStateoruseEffect. The destructured imports fail silently (they becomeundefined). Additionally, as a defensive forward-compatibility measure, a runtime check fortypeof React.lazy !== "function"is added — if a future React version stripslazyfromreact-server, the async server component fallback catches it (the RSC renderer natively supports async components).2. Route handler params not wrapped as thenable
Next.js 15+ changed route handler
paramsto be async (Promises). Route handlers thatawait paramscrash when params is a plain object frommatchPattern(). The RSC entry already hasmakeThenableParams()for this purpose but wasn't using it for route handler params.Note on
layout-segment-context.tsxThe
"use client"directive was kept in this file (it was not removed). Only the comment was updated to explain why the directive is required:LayoutSegmentProvideris imported directly by the RSC entry, and the"use client"boundary ensures it executes in the SSR/browser environment whereReact.createContextis available. Without it,getLayoutSegmentContext()returnsnullanduseSelectedLayoutSegmentsalways returns[].Solution
dynamic.ts
"use client"directive sodynamic()executes in the RSC module graph and can perform environment detectionlazy,Suspense,useState,useEffect) to namespace access (React.lazy,React.Suspense, etc.) — these are safely checked at runtimetypeof React.lazy !== "function", use an async server component pattern (the RSC renderer natively supports async components). In React 19.x,React.lazyis available inreact-serverso this path is forward-compatibility code.layout-segment-context.tsx
"use client"is required and what happens without itapp-rsc-entry.ts
paramswithmakeThenableParams()soawait paramsworks correctlyChanges
packages/vinext/src/shims/dynamic.ts"use client", add RSC async fallback path, useReact.*namespacepackages/vinext/src/shims/layout-segment-context.tsx"use client"is requiredpackages/vinext/src/entries/app-rsc-entry.ts{ params }→{ params: makeThenableParams(params) }Reproduction
Risk
Low-medium.
React.lazyis unavailable, which doesn't occur in React 19.x). SSR and client paths are functionally identical — only the import style changed from destructured to namespace."use client"retained as required.makeThenableParams()is already used for page params, layout params, metadata params, and slot params in the same file; this extends it to route handlers.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.