Next.js调用notFound()未返回universal目录下的404页面问题
notFound()未加载对应目录的not-found.tsx问题 我在Next.js服务器中通过Middleware处理多域名路由:当请求来自“通用域名”时,动态修改nextUrl.pathname,将请求路由到app目录下的universal/[...urlParts]页面。但遇到一个问题:在该页面中调用next/navigation的notFound()函数时,返回的是app根目录的not-found.tsx,而非universal目录下对应的not-found.tsx。
为设置通用页面的locale,我在page.tsx中先从服务获取页面信息,再通过unstable_setRequestLocale设置locale,之后调用notFound()。
依赖版本
next: 14.2.6 eslint-config-next: 14.2.6 react: 18.3.1 react-dom: 18.3.1 typescript: 5.0.4
Middleware代码
const withUniversal: MiddlewareFactory = (next: NextMiddleware) => { return async (request: NextRequest, _next: NextFetchEvent) => { if (request.nextUrl.pathname === "/robots.txt") { return NextResponse.next(); } const hostname = getHostNameFromRequest(request); if ( request.nextUrl.pathname.startsWith("/api/") || request.nextUrl.pathname.startsWith("/assets/") || request.nextUrl.pathname.startsWith("/favicon/") || request.nextUrl.pathname.startsWith("/~partytown/") || request.nextUrl.pathname.startsWith("/_next/static") || request.nextUrl.pathname.startsWith("/_next/image") || request.nextUrl.pathname.startsWith("/universal") ) { return await next(request, _next); } if ( Object.values(UniversalDomains).includes( hostname as UniversalDomains ) ) { const newPath = "/universal/" + hostname + request.nextUrl.pathname; request.nextUrl.pathname = newPath; return NextResponse.rewrite(new URL(newPath, request.url)); } const response = await next(request, _next); return response; }; };
项目目录结构
. ├── [locale] │ ├── (published_apps) │ ├── demo │ ├── not-found.tsx │ └── page-renderer ├── api │ ├── auth │ ├── embeddable │ ├── hello │ ├── newsletter │ ├── preview-deployments ├── layout.tsx ├── not-found.tsx ├── test.txt └── universal ├── [...urlParts] │ ├── layout.tsx │ └── page.tsx ├── layout.tsx └── not-found.tsx
日志信息
✓ Ready in 1341ms ✓ Compiled /middleware in 203ms (123 modules) ○ Compiling /universal/[...urlParts] ... ✓ Compiled /universal/[...urlParts] in 8.1s (7501 modules) Unable to find `next-intl` locale because the middleware didn't run on this request. See https://next-intl-docs.vercel.app/docs/routing/middleware#unable-to-find-locale. The `notFound()` function will be called as a result. GET /dbaizuvd 404 in 10150ms ○ Compiling /_not-found ... ✓ Compiled /_not-found in 3.8s (7504 modules) GET /favicon/favicon.co 404 in 3938ms
已尝试方法
- 在layout中手动设置带locale的
NextIntlClientProvider - 直接访问
http://localhost:3000/universal//ezfbaizeu,仍返回根目录的404页面
解决方案
这个问题的核心在于Next.js的notFound()函数会根据当前请求的逻辑路由层级查找对应not-found.tsx,但重写后的请求可能未正确保留universal目录的层级上下文,或unstable_setRequestLocale的调用干扰了路由上下文。
1. 确保重写请求的路由上下文正确
检查getHostNameFromRequest函数是否正确获取域名,避免生成的newPath出现格式错误(如多余斜杠)。同时,在使用NextResponse.rewrite时,不要覆盖请求的原始locale相关参数,确保路由层级上下文完整。
2. 提前在universal目录的layout中设置locale
不要在page.tsx中调用unstable_setRequestLocale,而是提前在universal的根layout中完成locale初始化,确保路由上下文在调用notFound()前已正确建立:
// universal/layout.tsx import { unstable_setRequestLocale } from 'next/server'; import { NextIntlClientProvider } from 'next-intl'; import { getMessages } from './messages'; export async function generateMetadata({ params }: { params: { urlParts: string[] } }) { // 从urlParts中提取实际hostname和locale信息 const hostname = params.urlParts[0]; const locale = 'en'; // 替换为业务逻辑获取的locale unstable_setRequestLocale(locale); return {}; } export default async function UniversalLayout({ children }: { children: React.ReactNode }) { const locale = 'en'; // 替换为业务逻辑获取的locale const messages = await getMessages(locale); return ( <NextIntlClientProvider locale={locale} messages={messages}> {children} </NextIntlClientProvider> ); }
3. 手动渲染universal/not-found.tsx(替代方案)
如果上述方法无效,可以放弃调用notFound(),直接在page.tsx中渲染universal目录下的404组件:
// universal/[...urlParts]/page.tsx import NotFound from '../../universal/not-found'; export default async function UniversalPage({ params }: { params: { urlParts: string[] } }) { const pageInfo = await fetchPageInfo(params); if (!pageInfo) { // 直接渲染universal下的not-found组件 return <NotFound />; } // 正常渲染页面逻辑 return <div>...</div>; }
4. 调整next-intl的Middleware顺序
日志提示next-intl找不到locale,说明你的next-intl Middleware可能未正确处理重写后的请求。确保withUniversal Middleware在next-intl Middleware之前执行,且覆盖所有需要处理的路径:
// middleware.ts import createMiddleware from 'next-intl/middleware'; import { withUniversal } from './withUniversal'; const intlMiddleware = createMiddleware({ locales: ['en', 'zh'], defaultLocale: 'en' }); // 确保withUniversal先执行,处理域名重写后再交给next-intl处理locale export default withUniversal(intlMiddleware); export const config = { matcher: ['/((?!api|assets|favicon|~partytown|_next).*)'] };
内容的提问来源于stack exchange,提问作者almerion

