
本文讲解如何在 Next.js App Router 中正确分离客户端交互逻辑与服务端静态文件读取,解决 use client 组件中无法直接使用 glob 等 Node.js 服务端 API 的问题,并提供基于 Route Handler + use Hook 的标准实践。
本文讲解如何在 next.js app router 中正确分离客户端交互逻辑与服务端静态文件读取,解决 `use client` 组件中无法直接使用 `glob` 等 node.js 服务端 api 的问题,并提供基于 route handler + `use` hook 的标准实践。
在 Next.js 13+ 的 App Router 架构下,"use client" 指令标记的组件运行在浏览器环境中(客户端组件),无法访问 Node.js 内置模块(如 fs, glob, crypto)或执行服务端文件系统操作。你遇到的错误(ReferenceError: glob is not defined 或 Cannot use 'glob' in client component)正是这一限制的典型表现——app/ui/Gallery.tsx 虽被声明为 Server Component(无 "use client"),但它被嵌套在 Home 这个客户端组件中渲染,且其内部调用 glob("public/images/*") 会在构建/请求时尝试在客户端执行,必然失败。
✅ 正确解法:职责分离 + 客户端数据消费
应将静态资源枚举(glob)严格保留在服务端环境(如 Route Handler 或 Server Component),客户端仅负责发起请求、响应状态变化并渲染结果。
✅ 推荐实现:Route Handler + use Hook(React 18.3+)
1. 创建服务端 API 端点(app/api/route.ts)
// app/api/route.ts
import { glob } from 'glob';
import path from 'path';
export async function GET() {
try {
// 注意:路径需相对于项目根目录,且 `glob` 默认不支持 `public/` 下的相对路径
// 推荐写法:使用绝对路径 + `path.join(process.cwd(), 'public', 'images', '*')`
const images = await glob(
path.join(process.cwd(), 'public', 'images', '*.{jpg,jpeg,png,gif}')
);
// 返回相对于 `/` 的 URL 路径(Next.js 静态资源通过 `/images/xxx.png` 访问)
const publicPaths = images.map((file) =>
file.replace(process.cwd(), '').replace(/\/g, '/').replace('/public', '')
);
return Response.json(publicPaths);
} catch (error) {
console.error('Failed to fetch gallery images:', error);
return Response.json({ error: 'Failed to load images' }, { status: 500 });
}
}
⚠️ 注意事项:
glob必须在服务端运行,因此只能放在app/api/、Server Components 或generateStaticParams中;process.cwd()在 Vercel 环境中指向部署根目录,本地开发也一致,是安全的;- 使用
*.{jpg,jpeg,png,gif}显式指定扩展名,避免匹配隐藏文件或非图像资源。
2. 客户端 Gallery 组件(app/ui/Gallery.tsx)
// app/ui/Gallery.tsx
'use client';
import { use } from 'react';
import Image from 'next/image';
// 在客户端组件顶层预取数据(自动 suspense,无需 useEffect)
const fetchImages = async () => {
const res = await fetch('/api');
if (!res.ok) throw new Error('Failed to load gallery');
return res.json();
};
const Gallery = ({ isSearching }: { isSearching: boolean }) => {
const images = use(fetchImages); // ✅ React 18.3+ `use` Hook,专为 Suspense 数据流设计
return (
<div classname="{`flex" flex-wrap gap-4 transition-opacity duration-300 :>
{images.map((src: string) => (
<div key="{src}" classname="relative w-48 h-48 overflow-hidden rounded-lg shadow-sm">
<image src="%7Bsrc%7D" alt="{`Gallery" item: fill classname="object-cover" sizes="(max-width: 768px) 100vw, 48rem"></image>
</div>
))}
</div>
);
};
export default Gallery;
✅ 关键优势:
dmap-jsapi-skill下载DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
use(fetchImages)自动触发 Suspense,配合<suspense fallback="{...}"></suspense>可优雅处理加载态;- 无
useState/useEffect,零副作用,符合服务端渲染(SSR)友好原则;isSearching状态由父组件控制,Gallery 纯响应式,符合 React 单向数据流。
3. 主页面(app/page.tsx)保持简洁
// app/page.tsx —— 无需 'use client',除非有交互逻辑
import Gallery from './ui/Gallery';
import SearchBar from './ui/SearchBar';
import { useState } from 'react';
export default function Home() {
const [isSearching, setIsSearching] = useState(false);
return (
<div classname="max-w-5xl mx-auto mt-10 px-4">
<searchbar setissearching="{setIsSearching}"></searchbar><gallery issearching="{isSearching}"></gallery>
</div>
);
}
4. 增强版 SearchBar(推荐添加 onBlur 复位)
// app/ui/SearchBar.tsx
'use client';
import { Dispatch, SetStateAction } from 'react';
export default function SearchBar({
setIsSearching,
}: {
setIsSearching: Dispatch<setstateaction>>;
}) {
return (
<div classname="mb-8">
<input type="text" placeholder="Search gallery..." classname="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-base" onfocus="{()"> setIsSearching(true)}
onBlur={() => setIsSearching(false)} // ✅ 用户失焦时恢复透明度
/>
</div>
);
}</setstateaction>
? 总结与最佳实践
- ❌ 禁止:在 Client Component 中
import { glob } from 'glob'或调用任何 Node.js 文件系统 API; - ✅ 必须:将
glob、fs.readdir等操作封装在app/api/Route Handler 或 Server Component 中; - ✅ 推荐:客户端使用
use(asyncFn)(React 18.3+)替代useEffect + useState获取初始数据,更简洁、可中断、SSR 友好; - ✅ 增强体验:为
opacity添加transition-opacity duration-300实现平滑渐变; - ? 生产注意:Vercel 环境中
glob可用,但确保public/images/目录已静态部署(即文件在构建时存在,非运行时生成)。
该方案完全遵循 Next.js 官方渲染模型(Rendering Docs),兼顾性能、可维护性与平台约束,是现代 Next.js 图库类交互的标准解法。











