Next13中带搜索参数路由触发‘仅使用绝对URL’错误求助
问题解决:Next.js 13 国际化Middleware兼容搜索参数报错处理
问题场景
基于Next.js 13 App Router实现服务端轮播组件,通过testimonialId搜索参数控制轮播项,组件的id属性来自页面的searchParams。同时配置了国际化Middleware自动补全路径中的locale,无搜索参数时功能正常,但手动修改路由或通过组件内Link跳转添加搜索参数时,触发**"Please use only absolute URLs"**错误。
相关代码
轮播组件代码
import Image from "next/image"; import React from "react"; import ArrowRight from "/public/assets/icons/landing/arrowright.svg"; import Link from "next/link"; interface Props { id: number; } const testimonials = [ // 轮播数据数组 ]; const Testimonials = ({ id }: Props) => { const generateRoute = (type: "forward" | "backward") => { if (type === "forward") { if (id < testimonials.length) { return `/?testimonialId=${+id + 1}`; } return `/?testimonialId=${0}`; } if (type === "backward") { if (id == 0) { return `/?testimonialId=${+testimonials.length}`; } return `/?testimonialId=${+id - 1}`; } return '' }; return ( <div className="mt-[56px] mb-[32px] bg-[#01164D] w-full flex justify-center items-center"> <div className="w-full max-w-[1216px] p-10 relative grid grid-cols-1 items-center lg:items-start gap-[24px] lg:ps-[308px] xl:ps-[274px]"> {/* 头像区域 */} <div className="absolute left-[50%] top-0 translate-x-[-50%] translate-y-[-50%] lg:top-[50%] lg:left-auto lg:right-[24px] xl:right-0 lg:translate-x-0" > <div className="relative w-[64px] h-[64px] lg:w-[250px] lg:h-[360px] "> <Image src={testimonials[id || 0].image} fill className="object-fit rounded-[50%] lg:rounded-[8px]" alt="用户头像" /> </div> </div> {/* 用户信息 */} <div className="flex flex-col items-center gap-[4px] lg:row-start-2 lg:items-start"> <h2 className="text-[16px] text-white font-bold leading-6"> {testimonials[id || 0].guy} </h2> <h3 className="text-[14px] text-white">{"مدرعامل هلدینگ ثابت"}</h3> </div> {/* 评论内容 */} <div className="bg-quotes bg-no-repeat bg-right-top "> <p className="text-[14px] font-light mt-[24px] text-white kg:text-start lg:max-w-[660px]"> {testimonials[id || 0].content} </p> </div> {/* 切换按钮 */} <div className="flex items-center justify-center gap-[20px] lg:justify-start"> <Link href={generateRoute("forward")} className="bg-white py-[10px] px-[8px] rounded-[4px] cursor-pointer" > <ArrowRight /> </Link> <Link href={generateRoute("backward")} className="bg-white py-[10px] px-[8px] rounded-[4px] rotate-180 cursor-pointer" > <ArrowRight /> </Link> </div> </div> </div> ); }; export default Testimonials;
国际化Middleware代码
import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import url from 'url' import { i18n } from "../i18n-config"; import { match as matchLocale } from "@formatjs/intl-localematcher"; import Negotiator from "negotiator"; const PUBLIC_FILE = /\.(.*)$/ export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; if ( request.nextUrl.pathname.startsWith("/_next") || request.nextUrl.pathname.includes("/api/") || PUBLIC_FILE.test(request.nextUrl.pathname) ) { return; } // 检查路径是否包含locale const pathnameIsMissingLocale = i18n.locales.every( (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}` ); // 无locale时重定向补全 if (pathnameIsMissingLocale) { const { searchParams } = request.nextUrl const search = searchParams.toString() const locale = 'fa' const redirectUrl = url.format({ pathname: `/${locale}${request.nextUrl.pathname}`, search }) return NextResponse.redirect(redirectUrl) } } export const config = { matcher: ["/((?!_next).*)"] };
错误原因
url.format()方法生成的是相对URL,而Next.js的NextResponse.redirect()要求必须传入绝对URL,当请求包含搜索参数时,这个问题会被触发。
解决方案
修改Middleware中的重定向逻辑,基于当前请求的绝对URL来构建完整的重定向地址,确保生成的是绝对URL。
修改后的Middleware代码
import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { i18n } from "../i18n-config"; import { match as matchLocale } from "@formatjs/intl-localematcher"; import Negotiator from "negotiator"; const PUBLIC_FILE = /\.(.*)$/ export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; if ( request.nextUrl.pathname.startsWith("/_next") || request.nextUrl.pathname.includes("/api/") || PUBLIC_FILE.test(request.nextUrl.pathname) ) { return; } const pathnameIsMissingLocale = i18n.locales.every( (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}` ); if (pathnameIsMissingLocale) { // 基于当前请求URL创建绝对URL对象 const redirectUrl = new URL(request.url); // 获取用户偏好的locale(替换原硬编码逻辑,贴合国际化设计) const negotiatorHeaders: Record<string, string> = {}; request.headers.forEach((value, key) => (negotiatorHeaders[key] = value)); const languages = new Negotiator({ headers: negotiatorHeaders }).languages(); const locale = matchLocale(languages, i18n.locales, i18n.defaultLocale); // 更新路径,添加locale前缀 redirectUrl.pathname = `/${locale}${pathname}`; // 保留原搜索参数 redirectUrl.search = request.nextUrl.search; return NextResponse.redirect(redirectUrl); } } export const config = { matcher: ["/((?!_next).*)"] };
关键修改点
- 移除
url模块依赖,改用new URL(request.url)创建绝对URL对象,确保重定向地址是完整的绝对路径。 - 替换硬编码的
locale = 'fa',用negotiator和intl-localematcher获取用户偏好的locale,符合国际化设计逻辑。 - 直接复用
request.nextUrl.search保留原搜索参数,无需手动转换searchParams。
额外优化(可选)
轮播组件中的generateRoute函数生成的是相对路径,可改为基于当前locale路径生成,避免Middleware重复处理:
// 假设组件能获取到当前locale(可从page的props或next/navigation的useParams获取) const generateRoute = (type: "forward" | "backward", locale: string) => { let newId = id; if (type === "forward") { newId = id < testimonials.length - 1 ? id + 1 : 0; } else { newId = id === 0 ? testimonials.length - 1 : id - 1; } return `/${locale}/?testimonialId=${newId}`; };
内容的提问来源于stack exchange,提问作者Abolfazl Ghodrati
相关产品推荐
相关产品推荐

