diff --git a/crates/next-core/src/next_manifests/client_reference_manifest.rs b/crates/next-core/src/next_manifests/client_reference_manifest.rs index 96766f3a2864a..4c15d694c7f5a 100644 --- a/crates/next-core/src/next_manifests/client_reference_manifest.rs +++ b/crates/next-core/src/next_manifests/client_reference_manifest.rs @@ -104,16 +104,6 @@ impl ClientReferenceManifest { (Vec::new(), false) }; - entry_manifest.client_modules.module_exports.insert( - get_client_reference_module_key(&server_path, "*"), - ManifestNodeEntry { - name: "*".into(), - id: (&*client_module_id).into(), - chunks: client_chunks_paths, - r#async: client_is_async, - }, - ); - if let Some(ssr_chunking_context) = ssr_chunking_context { let ssr_chunk_item = ecmascript_client_reference .ssr_module @@ -154,6 +144,19 @@ impl ClientReferenceManifest { (Vec::new(), false) }; + entry_manifest.client_modules.module_exports.insert( + get_client_reference_module_key(&server_path, "*"), + ManifestNodeEntry { + name: "*".into(), + id: (&*client_module_id).into(), + chunks: client_chunks_paths, + // This should of course be client_is_async, but SSR can become async + // due to ESM externals, and the ssr_manifest_node is currently ignored + // by React. + r#async: client_is_async || ssr_is_async, + }, + ); + let mut ssr_manifest_node = ManifestNode::default(); ssr_manifest_node.module_exports.insert( "*".into(), @@ -161,7 +164,8 @@ impl ClientReferenceManifest { name: "*".into(), id: (&*ssr_module_id).into(), chunks: ssr_chunks_paths, - r#async: ssr_is_async, + // See above + r#async: client_is_async || ssr_is_async, }, ); diff --git a/crates/next-core/src/next_server/context.rs b/crates/next-core/src/next_server/context.rs index a0865d56f4856..2aa23c032e32c 100644 --- a/crates/next-core/src/next_server/context.rs +++ b/crates/next-core/src/next_server/context.rs @@ -182,8 +182,7 @@ pub async fn get_server_resolve_options_context( project_path, project_path.root(), ExternalPredicate::Only(Vc::cell(external_packages)).cell(), - // app-ssr can't have esm externals as that would make the module async on the server only - *next_config.import_externals().await? && !matches!(ty, ServerContextType::AppSSR { .. }), + *next_config.import_externals().await?, ); let mut custom_conditions = vec![mode.await?.condition().to_string().into()]; diff --git a/test/e2e/app-dir/app-external/app-external.test.ts b/test/e2e/app-dir/app-external/app-external.test.ts index 7faa89f365921..4959d506df187 100644 --- a/test/e2e/app-dir/app-external/app-external.test.ts +++ b/test/e2e/app-dir/app-external/app-external.test.ts @@ -264,6 +264,11 @@ describe('app dir - external dependency', () => { expect(html).toContain('hello') }) + it('should support client module references with SSR-only ESM externals', async () => { + const html = await next.render('/esm-client-ref-external') + expect(html).toContain('client external-pure-esm-lib') + }) + it('should support exporting multiple star re-exports', async () => { const html = await next.render('/wildcard') expect(html).toContain('Foo') diff --git a/test/e2e/app-dir/app-external/app/esm-client-ref-external/client.js b/test/e2e/app-dir/app-external/app/esm-client-ref-external/client.js new file mode 100644 index 0000000000000..11fe92ee0aa66 --- /dev/null +++ b/test/e2e/app-dir/app-external/app/esm-client-ref-external/client.js @@ -0,0 +1,7 @@ +'use client' + +import name from 'esm' + +export function Client() { + return