如何在 TanStack Start v1 中验证 URL 中的 locale 参数

优雅地处理不受支持的语言代码

问题

当语言代码成为 URL 结构的一部分时,它们就变成了需要验证的用户输入。用户可以在 locale 段中手动输入任意字符串——如 /xx/about/gibberish/contact/typo123/products——就像输入有效代码 /en/about/fr/contact 一样简单。如果不进行验证,应用可能会尝试加载不存在的语言环境的翻译,导致内容异常或崩溃。每一个无效的 locale 都可能成为用户无法恢复或跳转到正常页面的死胡同。

未验证的 locale 参数会导致不可预测的行为。应用可能会悄无声息地加载失败,渲染出混合的兜底内容和缺失内容,或者在访问翻译 key 时抛出运行时错误。用户如果点击了损坏的链接或输错了 URL,将无法获得明确的反馈,不知道哪里出错,也不知道如何修复。

解决方案

在路由的 beforeLoad 函数中,将 URL 中的 locale 参数与支持的语言环境列表进行校验。当 locale 无效或缺失时,将用户重定向到带有默认 locale 的有效 URL,或抛出 not-found 错误以显示友好的错误页面。这样可以确保只处理受支持的 locale,并让用户始终落在可翻译的有效页面上。

beforeLoad 函数会在路由加载前运行,是检查 locale 的理想位置。通过抛出 redirect()notFound() 错误,可以防止路由在无效 locale 下渲染,并引导用户回到正常状态。

步骤

1. 定义支持的语言区域

创建一个包含有效语言区域代码的常量数组,并实现类型安全的校验函数。

const SUPPORTED_LOCALES = ["en", "fr", "es", "de"] as const;

type Locale = (typeof SUPPORTED_LOCALES)[number];

function isValidLocale(locale: string | undefined): locale is Locale {
  return SUPPORTED_LOCALES.includes(locale as Locale);
}

这样可以为支持的语言区域提供单一数据源,并实现 TypeScript 可识别的可复用校验函数。

2. 创建带有语言区域校验的布局路由

使用可选的语言区域参数,并在 beforeLoad 中进行校验。

import { createFileRoute, redirect } from "@tanstack/react-router";

const DEFAULT_LOCALE: Locale = "en";

export const Route = createFileRoute("/{-$locale}")({
  beforeLoad: ({ params }) => {
    const { locale } = params;

    if (locale && !isValidLocale(locale)) {
      throw redirect({
        to: "/{-$locale}",
        params: { locale: undefined },
        replace: true,
      });
    }

    return {
      locale: (locale as Locale) || DEFAULT_LOCALE,
    };
  },
});

beforeLoad 函数会检查 locale 参数。如果存在但无效,则用户会被重定向到没有 locale 前缀的相同路径(即默认语言区域)。校验通过的 locale 会在 context 中返回,供子路由使用。

3. 在嵌套路由中校验语言区域

对于需要 locale 的路由,进行校验,如果无效则抛出 notFound()

import { createFileRoute, notFound } from "@tanstack/react-router";

export const Route = createFileRoute("/{-$locale}/products")({
  beforeLoad: ({ params }) => {
    const { locale } = params;

    if (locale && !isValidLocale(locale)) {
      throw notFound();
    }

    return {
      locale: (locale as Locale) || DEFAULT_LOCALE,
    };
  },
  component: ProductsPage,
});

function ProductsPage() {
  const { locale } = Route.useRouteContext();
  return <div>Products in {locale}</div>;
}

这种方式会在 locale 无效时显示 not-found 页面,而不是重定向。当你希望明确告知 URL 格式错误而不是自动修正时,这种方式非常有用。

4. 为无效语言区域添加 not-found 组件

在根路由上定义 notFoundComponent,以优雅地处理无效 locale 错误。

import { createRootRoute } from "@tanstack/react-router";

export const Route = createRootRoute({
  notFoundComponent: () => {
    return (
      <div>
        <h1>Page Not Found</h1>
        <p>The language or page you requested does not exist.</p>
        <a href="/">Go to home page</a>
      </div>
    );
  },
});

notFound()beforeLoad 抛出时,该组件会渲染,为用户提供清晰的提示信息和返回有效页面的方式。