You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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).*)"]
};

关键修改点

  1. 移除url模块依赖,改用new URL(request.url)创建绝对URL对象,确保重定向地址是完整的绝对路径。
  2. 替换硬编码的locale = 'fa',用negotiator和intl-localematcher获取用户偏好的locale,符合国际化设计逻辑。
  3. 直接复用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 17:02:03