前言
这个问题是在较新的Next.js版本(v15+)报的错误,在较老的版本(v14-),你可能会报下述的错误:
这个问题其实在Next.js的issues里提到了很多次,记录一下
有兴趣的朋友们可以看下Next.js里的issues,他们貌似近期还在排查原因(2025年2月初),例如这一个:Error when using namespace/compound components in React Server Component #75192
本文对此问题产生的根源并无深入的研究,只想解决的朋友们可以直接转到解决方案
现象
本项目技术栈是Next.js,采用的App Router模式,如果采用老的Page Router可能没这问题(不清楚)
具体逻辑不放了,主要就是两点:
- 因为用了hooks,所以使用到了
use client; - 用了一种传统的
function assignment pattern方式导出组件namespace(对于Object.assign分配namespace的情况可能也有此问题)
Sidebar.tsx
'use client'
export const SidebarRoot = (...) => {
...
};
export const SidebarWrapper = (...) => {
...
};
export const SidebarHeader = (...) => {
...
};
export const Sidebar = {
Root: SidebarRoot,
Wrapper: SidebarWrapper,
Header: SidebarHeader,
};
layout.tsx
import { Sidebar } from '@/components/ui/Layout';
export default function EditorLayout(...) {
return (
...
<Sidebar.Root>
<Sidebar.Header>123</Sidebar.Header>
</Sidebar.Root>
...
);
}
问题分析
首先要说明,主要问题出在上面这里:
export const Sidebar = {
Root: SidebarRoot,
Wrapper: SidebarWrapper,
Header: SidebarHeader,
};
我对此问题产生原因的猜测是:和树摇类似,RSC的组件导入难以识别这种类型(将组件直接分配为函数组件属性而非独立导入),Next.js中的optimisers无法将其解析为将使用的组件。
如果您有对这个问题根源的更详细解释或更深入的了解,请务必在文章下评论,我对此非常感兴趣,感谢!
解决方案
- 暴力的解决方案:你在使用到该组件的地方,例如这里的
layout.tsx,在顶部直接use client。确实没毛病,但是这种解决方法过于粗糙,首先SSR肯定是建议在子节点的地方用use client以尽可能减少性能问题;另外这也没有理解到问题的根源(虽然本文也没有深入了解,目前issues里好像也还在讨论) - 将需要用到
use client的组件拉出去单独文件,然后再引入,好像也解决了。但是对于短函数组件,单独拉出去似乎也不够优雅 - 直接导出导入独立组件。该方案等于抛弃了
namespace - (推荐)另一种
namespace导入导出方式,详见下述
这里讲一下第四种方案,其实非常简单:
sidebar.tsx
'use client'
const SidebarRoot = (...) => {
...
};
const SidebarWrapper = (...) => {
...
};
const SidebarHeader = (...) => {
...
};
export { SidebarRoot as Root, SidebarWrapper as Wrapper, SidebarHeader as Header };
layout.tsx
import * as Sidebar from '@/components/ui/Layout';
export default function EditorLayout(...) {
return (
...
<Sidebar.Root>
<Sidebar.Header>123</Sidebar.Header>
</Sidebar.Root>
...
);
}
参考
Multipart Namespace Components: Addressing RSC and Dot Notation Issues