命令行搭建

npx create-next-app@latest

我这里安装的是15版本,根据提示选择需要的库

Ok to proceed? (y) y
√ What is your project named? ... my-app
√ Would you like to use TypeScript? ... No / Yes
√ Which linter would you like to use? » ESLint
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like your code inside a `src/` directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to use Turbopack? (recommended) ... No / Yes
√ Would you like to customize the import alias (`@/*` by default)? ... No / Yes
√ What import alias would you like configured? ... @/*

安装成功后,基于求稳心态,我将react版本降到了18版本

npm install -D @types/react@18  @types/react-dom@18

npm install -S react@18 react-dom@18

运行

npm run dev

需要注意的是,我选择的是App Router 模式,因此页面都是在app目录下创建的,而访问路由就是app目录下的文件夹名,比如创建了app/home/page.tsx,那么访问路由就是 "/home"

并且创建的路由文件夹需要小写开头,遵循统一规范原则

配置国际化

新建il8n目录:

// src\i18n\navigation.ts
// 路由配置
import { createNavigation } from "next-intl/navigation";

import { routing } from "./routing";

// Next.js导航的轻量级封装
// 考虑路由配置的API
export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);
// src\i18n\request.ts
import { getRequestConfig } from 'next-intl/server';
import { hasLocale } from "next-intl";

import { routing } from './routing';

// 服务端获取文案配置
export default getRequestConfig(async ({ requestLocale }) => {
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
      ? requested
      : routing.defaultLocale;

  // 动态导入对应语言的文案(Next.js 会自动按需加载)
  const messages = (await import(`./messages/${requested}.json`)).default;

  return {
    locale,
    messages,
  };
});
// src\i18n\routing.ts
import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  locales: ["en", "zh"],
  defaultLocale: "zh",
});

新建il8n/messages目录:src\i18n\messages\en.json     |    src\i18n\messages\zh.json

{
  "Home": {
    "title": "Home",
    "subtitle": "Welcome to Next.js App Router i18n",
    "button": "Switch Language"
  }
}


{
  "Home": {
    "title": "首页",
    "subtitle": "欢迎使用 Next.js App Router 国际化",
    "button": "切换语言"
  }
}
// next.config.ts
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";

// 指向配置文件
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');

const nextConfig: NextConfig = {
  // // 输出独立构建
  // output: 'standalone',
  // // 启用压缩
  // compress: true,
  // experimental: {
  //   // 启用服务端操作
  //   serverActions: {
  //     // 服务端操作请求体大小限制
  //     bodySizeLimit: "50mb",
  //   },
  //   // 优化包大小导入
  //   optimizePackageImports: [
  //     "@next/font",
  //     "@next/bundle-analyzer",
  //     "next-intl",
  //     "next-intl/server",
  //     "next-intl/routing",
  //     "next-intl/navigation",
  //   ],
  // },
  // typescript: {
  //   // 在构建时忽略类型错误
  //   ignoreBuildErrors: true,
  // },
  // eslint: {
  //   // 忽略 ESLint 错误
  //   ignoreDuringBuilds: true,
  // },
  // images: {
  //   // 允许 Next.js 访问本地图片
  //   remotePatterns: [
  //     {
  //       protocol: "http",
  //       hostname: "localhost",
  //     },
  //   ],
  // },
};

export default withNextIntl(nextConfig);

中间件文件为访问根目录public下的文件,调整正则:

// src\middleware.ts
import createIntlMiddleware  from 'next-intl/middleware';
import { NextRequest } from 'next/server';

import { routing } from './i18n/routing';

// 创建国际化中间件
const intlMiddleware = createIntlMiddleware(routing);

// 匹配需要拦截的路由(App Router 规则)
export const config = {
  matcher: [
    // 规则解读:
    // 1. 匹配所有路径,但排除:
    //    - /api/*:接口请求
    //    - /_next/*:Next.js 内置静态资源
    //    - /favicon.ico:网站图标
    //    - 以 . 结尾的路径(如 /logo.png、/style.css):public 下的静态资源
    //    - /images/*、/assets/* 等自定义静态资源目录(按需添加)
    '/((?!api|_next/static|_next/image|favicon.ico|.*\\..*|images|assets).*)'
  ]
};

// 自定义中间件逻辑
export default function middleware(request: NextRequest) {
  // 执行 next-intl 核心逻辑
  return intlMiddleware(request);
}

新建变量目录

// src\app\layout.tsx
import { ReactNode } from 'react';
import './globals.css'

export default function RootLayout({ children }: { children: ReactNode }) {
  return children;
}
// src/app/[locale]/layout.tsx
import { ReactNode } from 'react';
import { NextIntlClientProvider, useLocale, useMessages } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import { Metadata } from 'next';

import { routing } from '@/i18n/routing';

type Props = {
  children: ReactNode;
  params: { locale: string };
};


// 生成动态元数据
export async function generateMetadata({ params: paramsPromise }: { params: { locale: string } }): Promise<Metadata> {
   const params = await paramsPromise;

  // 服务端获取翻译函数
  const t = await getTranslations({
    locale: params.locale,
    namespace: 'Home',
  });
  return {
    title: t('title'),
    description: t('subtitle')
  };
}

// 生成所有语言的静态路由
export async function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export default function LocaleLayout({ children }: Props) {
  const locale = useLocale();
  const messages = useMessages();

  return (
    <html lang={locale}>
      <body>
        {/* 为客户端组件提供国际化上下文 */}
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
// src/app/[locale]/page.tsx(首页服务器组件)
import { useTranslations } from 'next-intl';

export default function HomePage({ params }: { params: { locale: string } }) {
  // 初始化翻译函数(指定命名空间)
  const t = useTranslations('Home');

  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('subtitle')}</p>
      {/* 语言切换按钮(后续实现) */}
      <button>{t('button')}</button>
    </div>
  );
}

删除文件

src\app\page.tsx

按需安装shadcn组件

npx shadcn@latest add button

问题集锦

问题1:
Event handlers cannot be passed to Client Component props.

<div className=... onClick={function onClick} children=...> ^^^^^^^^^^^^^^^^^^ If you need interactivity, consider converting part of this to a Client Component.
问题2:

window is not defined

解决办法:

这个错误表明你正在尝试将事件处理器传递给一个客户端组件,但是当前的组件没有被标记为客户端组件。在 Next.js App Router 中,你需要显式声明哪些组件是客户端组件。

以下是解决这个问题的方法:

        在文件顶部添加 "use client" 指令,将报错的这个页面组件转换为客户端组件

项目中遇到的其他问题:

1、'use client' 页面引入了 'server-only' 函数
解决办法:去掉'use client',并将页面导出改成异步函数导出

2、在报错页面提示:useSearchParams() should be wrapped in a suspense boundary at page 
解决办法:使用 useSearchParams() 的组件必须被 Suspense 包裹。将使用 useSearchParams() 的逻辑提取到内部组件,并用 Suspense 包裹。

3、本地访问慢,缓存未生效。

命令行加入指定值 next dev --turbopack

替换正则

将 onClick="printSpec()" onClick="exportSpec(3)" 改成 onClick={() => { // printSpec() }} onClick={() => { // exportSpec(3) }}

// 搜索框(精准匹配函数调用)
\bonClick="([^"]+)"

// 替换框(生成带注释的 JSX 函数)
onClick={() => {\n  // $1\n}}

正则匹配 rows="3", rows="4", 将其替换成 rows={3} rows={4}

// 搜索
rows(\s*)=(\s*)"(\d+)"

// 替换
rows$1=$2{$3}

vscode 搜索正则匹配input ,就为了在结尾加个 />

// 搜索
<input(?![^>]*/>)([^>]*)>

// 替换
<input$1 />

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐