mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
7d0203d421
* fix: RSC compatibility for dynamic() and layout segment context Three related fixes for React Server Component environments: 1. **dynamic.ts: Remove "use client" and add RSC async path** The `"use client"` directive forced `next/dynamic` into a client component boundary, but `dynamic()` should work in server components too. In the RSC environment, `React.lazy` is not available (the `react-server` condition exports a stripped-down React). Added a runtime check: when `React.lazy` is not a function, use an async server component pattern instead (the RSC renderer natively supports async components). Also switched from destructured imports (`lazy`, `Suspense`, `useState`, `useEffect`) to `React.lazy`, `React.Suspense`, etc. to avoid importing names that don't exist under the `react-server` condition. 2. **layout-segment-context.tsx: Remove "use client"** This module is imported directly by the RSC entry. The `"use client"` directive created a client component boundary that breaks the RSC rendering pipeline. `getLayoutSegmentContext()` already returns `null` when `React.createContext` is unavailable (RSC), and the `LayoutSegmentProvider` gracefully falls back to passing children through unchanged. 3. **app-rsc-entry.ts: Wrap route handler params with makeThenableParams** Next.js 15+ changed route handler params to be async (Promises). Route handlers that `await params` crash when params is a plain object. `makeThenableParams()` wraps the object so it's both a Promise and has synchronous property access. * chore: fix formatting and update entry-templates snapshots after merge with main * fix: restore 'use client' in layout-segment-context to fix useSelectedLayoutSegment(s) Removing 'use client' caused LayoutSegmentProvider to run in the RSC environment where React.createContext is undefined. getLayoutSegmentContext() returned null, the provider became a no-op, and useSelectedLayoutSegments always returned [] instead of the actual segments. The 'use client' boundary is required so the RSC bundler renders this component in the SSR/browser environment where createContext works. * test: add regression tests for RSC dynamic() and route handler await params Add two regression tests for the fixes in PR #466: 1. Route handler await params (Next.js 15 async params pattern): Tests /api/catch-all/[...slugs] which uses `await params` in the handler — verifies that makeThenableParams() correctly wraps params so both await and direct property access work. 2. dynamic() in RSC (async component path): Adds a pure server component fixture that uses dynamic() without "use client". In the RSC environment React.lazy is unavailable, so dynamic() must fall back to the async component pattern. Tests that the dynamically-loaded component renders in the HTML output. * fix: address bonk review comments on dynamic.ts RSC async path - Add comments explaining LoadingComponent is intentionally ignored in the RSC async fallback path (parent Suspense boundaries handle loading states; error handling defers to nearest error boundary) - Add comment explaining the 'as unknown as ComponentType<P>' cast is safe because the RSC renderer natively supports async components, but TypeScript's ComponentType<P> doesn't account for async return types - Add unit tests for the AsyncServerDynamic path by stubbing React.lazy to undefined (simulating a react-server condition without lazy). Verifies: displayName, async resolution, bare component exports, and that LoadingComponent is correctly ignored. Note: React 19.x exports React.lazy from the react-server condition, so typeof React.lazy !== 'function' does not trigger in current React. The AsyncServerDynamic path is defensive forward-compatibility code. The unit tests stub React.lazy to exercise it directly. * chore: remove unused vi import from dynamic.test.ts * docs: correct inaccurate React.lazy/RSC comments throughout React 19.x exports React.lazy from the react-server condition, so the typeof React.lazy !== 'function' guard does not trigger in current React. The AsyncServerDynamic path is defensive forward-compatibility code only — it does not execute today. Update all comments that incorrectly claimed React.lazy is unavailable in the react-server environment: - dynamic.ts: top-level doc comment and guard comment - dynamic-rsc.tsx fixture: comment explaining the RSC path - rsc-dynamic/page.tsx fixture: explain which path actually executes - nextjs-compat/dynamic.test.ts: clarify that the integration test exercises LazyServer + Suspense, not AsyncServerDynamic --------- Co-authored-by: James <james@eli.cx>