Skip to content

fix: add 'use client' to next/dynamic shim for App Router RSC compatibility#7

Merged
southpolesteve merged 1 commit intomainfrom
fix/dynamic-ssr-false-use-client
Feb 24, 2026
Merged

fix: add 'use client' to next/dynamic shim for App Router RSC compatibility#7
southpolesteve merged 1 commit intomainfrom
fix/dynamic-ssr-false-use-client

Conversation

@threepointone
Copy link
Collaborator

Problem

next/dynamic with ssr: false produces a blank page when used from the App Router with the Cloudflare Vite plugin. The dynamically imported component never loads on the client.

Root cause

The shims/dynamic.ts module wasn't marked as a client module. When a server component calls dynamic(), the RSC serializer executes the function on the server. For ssr: false, the server path returns null, which is serialized into the RSC payload and sent to the client as-is. The client never runs the mount-on-client code path.

In Next.js's App Router, next/dynamic resolves to a "use client" module (app-dynamic.tsx). The RSC serializer emits a client reference instead of executing the function inline.

Fix

Added "use client" to the top of packages/vinext/src/shims/dynamic.ts. This makes the dynamic shim a client module, matching Next.js behavior.

Both ssr: true and ssr: false work correctly:

  • ssr: true — SSR environment resolves the client reference and renders the component to HTML. Client hydrates. No behavior change.
  • ssr: false — SSR renders null (via isServer check). Client hydrates, useEffect fires → lazy component loads.

Test fixture updates

Existing test fixtures that called dynamic() from server modules now include "use client" — this is required since dynamic() is a client export. This matches Next.js's App Router behavior where you must call dynamic() from a client module.

Added a new E2E test (ssr:false from server component loads after hydration) with a server component page that imports a client wrapper using dynamic() with ssr: false.

All 47 test files pass (1803 tests).

…bility

When a server component imports next/dynamic, the RSC serializer must
treat it as a client boundary. Without 'use client', the dynamic()
function executes inline in the RSC environment — for ssr:false, this
serializes null into the RSC payload and the client never loads the
component.

Adding 'use client' matches Next.js's App Router behavior where
next/dynamic is a client module. The RSC serializer emits a client
reference, and the client executes the full dynamic logic including
the ssr:false mount-on-client behavior.

Updated test fixtures that called dynamic() from server modules to
include 'use client' — this is required since dynamic() is now a
client export and can only be called from client modules.

Added E2E test for ssr:false from a server component page.
@github-actions
Copy link

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original

@southpolesteve southpolesteve merged commit 493a4c1 into main Feb 24, 2026
17 checks passed
@southpolesteve southpolesteve deleted the fix/dynamic-ssr-false-use-client branch February 24, 2026 17:13
southpolesteve added a commit that referenced this pull request Feb 27, 2026
9 Playwright tests covering all middleware behaviors:
- Redirect: /old-page → /about with correct URL change
- Rewrite: /rewritten serves /ssr content at original URL
- Block: /blocked returns 403 with 'Access Denied' body
- Header injection: x-middleware-test header on matched pages
- Matcher exclusion: /api routes don't get middleware headers
- Index page gets middleware headers
- Redirect target page is fully functional (client-side nav works)
- Rewrite preserves getServerSideProps data and __NEXT_DATA__
- Static file requests not affected by middleware

Closes #7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants