1 Next.js 静态导出网页 SEO 优化实践指南

在构建极速的现代前端网站时,Next.js 的静态导出 (output: 'export') 是不二之选。它极大降低了服务器算力门槛与运维成本。然而,在剥离了 Node.js SSR 服务器后,如何在完全纯静态的产物下实现国际化 (i18n) SEO?

本文将复盘我在开发 Monticule Tech (丘原科技) 官网时的全套 SEO 优化思路。

1.1 为什么弃用 next-seo?直接拥抱原生 Metadata API

现在,如果你问 AI,在 Next.js 要怎么做 SEO,AI 大概率会告诉你用 next-seo 这个第三方库。我也被 AI 坑了一下,直到我发现,实际上 Next.js 自己就内置了 SEO 工具。

在 Pages Router 时代,开发者往往会依赖 next-seo 等第三方库去强行把 <meta> 标签塞入文档头。但在全新的 App Router 下,Next.js 官方已经内置了一套极其强大的、开箱即用的 Metadata API

我们只需要在 layout.tsx 或独立的子页面 page.tsx 中导出一个(甚至支持异步的)generateMetadata 函数。这不仅框架运行效率更高、带来零多余客户端打包体积,还能通过树状嵌套天生支持 OpenGraph、Canonical、Hreflang 等高度复杂的标签生成。

1.2 国际化 (i18n) 各子页面的动态独立 TDK 注入

对于 SEO 而言,我们通常需要一个富含长尾关键字组合的描述。

💡 贴士:专业的 SEO 体检报告指出,英文的 meta description 强烈建议人工精简压缩至 110 - 160 个字符之间。相比之下,中文因为信息密度高,往往可以更从容地布置关键字。

随后,在每个单独页面的 Server Component 内通过读取字典,拼装出独立的 Title 和 Description:

举个栗子:

// src/app/[lang]/about/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const { lang } = await params;
  const dict = await getDictionary(lang);
  
  return {
    title: dict.seo.about_title,
    description: dict.seo.about_desc,
    openGraph: {
      title: dict.seo.about_title,
      description: dict.seo.about_desc,
      // 动态注入社交分享横图
      images: ["/your-image-here.webp"], 
      // ...
    }
  };
}

1.3 多语言站点的痛点:用 Canonical 与 Hreflang 治理索引混乱

当你有一个 /en/about 与一个 /zh/about(甚至还有默认隐藏语言标识的 /about)时,搜索引擎极易将它们判为内容完全雷同的“克隆页面”从而施加重复抓取惩罚。

解决办法是通过 Metadata.alternates 清晰地交代三件事:

  1. 这个页面在当前语系下的绝对标准权威地址 (canonical) 是什么?

  2. 这个页面在全球范围内,默认推荐访客进入的大门 (x-default) 在哪?

  3. 你的另外几个“双胞胎异父兄弟”(替代语言 hreflang)分别位于哪个网址?

// 在 generateMetadata 的返回值中:
alternates: {
  canonical: "/zh/about/", // 指示百度/Google这是源自何处的唯一“干净链接”
  languages: {
    "x-default": "/about/", // 提供给无法匹配到相关语言的全球过客
    "en": "/about/", 
    "zh": "/zh/about/",
  },
}

1.4 防不胜防:彻底消灭静态导出的 301 重定向陷阱 (Trailing Slashes)

在用专业的 SEO 工具跑了一遍全站深度抓取后,我发现遇到了大量的警告:301 Redirect in sitemap

1.4.1 根本原因在哪里?

如果你使用了 output: 'export' 将 Next.js 项目编译成了静态的 HTML 并打包送上服务器(如 Nginx、CDN 等)。从物理文件层面来说,Next 会将路由构建成一个个包裹着 index.html文件夹(例如:out/products/index.html)。

当爬虫顺着常规不带斜杠的代码链接去强行访问 /products 时,底层宿主静态服务器判定它是目录,并会自动下发一个 HTTP 301 Moved Permanently 的指令,要求来宾再跳转跑一遍带物理斜杠的 /products/。这对于惜时如金的爬虫抓取预算 (Crawl Budget) 是完全多余的性能浪费,甚至削弱 SEO 评分。

1.4.2 解法:全部使用尾部斜杠 (Trailing Slash)

  1. 改写 <Link>:将页面内诸如 Navbar 与大按钮里所有的跳转 <Link href="/products"> 修改为严格精确的 <Link href="/products/">

  2. 改写元数据:如上一节中所示,确保 canonicalopenGraph.url 指向出去的数据全部显式携带 / 结尾

  3. 改写 Sitemap:主动修改你的站点地图配置文件,确保主动提报给爬虫们的网址列表,更是天生就自带无损斜杠的。

1.5 最后一块拼图:生成 Sitemap 与 Robots

Next.js v13 新增了优雅的 sitemap.tsrobots.ts 生成规约,但在开启了完全静态输出(output: export)时,如果这两个特殊文件里包裹了基于请求的动态生成意图,npm run build 命令行会在数据抓取时直接抛出错崩溃:

Error: export const dynamic = "force-static"/export const revalidate not configured on route "/robots.txt" ...

一步到位的解决:直接在包含映射表的 sitemap.tsrobots.ts 文件最最最首行代码处,直接加入强制静态的系统级申明锁:

// src/app/sitemap.ts
export const dynamic = "force-static"; // <- 必须在第 1 行!
import { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
  const routes = ['', '/about/', '/products/', '/zh/', '/zh/about/', '/zh/products/'];
  return routes.map((route) => ({
    url: `https://monticule.tech${route}`,
    lastModified: new Date().toISOString(),
    // ...
  }));
}

这样子,静态构建时它们就会直接在根目录下为你产出标准的 sitemap.xmlrobots.txt 平面文件。

1.6 总结

不必盲目搬弄各种沉重的第三方库。深入吃透 Next.js App Router 的自带特性,把国际化字典剥离出专门的高能 SEO 字段落点、安排稳妥的 OpenGraph 横图、顺应静态服务物理特性的尾部斜杠体系,在最后补上一把 force-static 的编译锁。

你的纯静态导出的 Web 应用,也完全能享受到地表最满分的搜索引擎挚爱评分。

声明:本文由 AI 辅助生成