发布于 2026-01-06 7 阅读
0

NextJS 应用路由中实现功能性 SEO 的实用指南:静态和动态元数据

NextJS 应用路由中实现功能性 SEO 的实用指南:静态和动态元数据

隆重推出……

首先,无需纠结 ReactJS 和 NextJS 哪个更好。如果您正在考虑优化您的网站/Web 应用,例如:

  1. 搜索引擎——抓取和索引——让您的网站和内容在搜索结果中获得排名
  2. 如果您正在构建动态元数据,以便在分享链接时获得精美的链接描述和图像预览……那么您就知道 NextJS 是您的最佳选择。

我们暂且不讨论页面路由和应用路由的区别,我个人非常喜欢应用路由架构带来的强大功能。从便捷的文件路由、更灵活的动态分段路由模式、以及用于服务器端操作的通用路由等等,应用路由功能非常强大。因此,本指南将只介绍如何使用应用路由来实现 SEO。

入门

首先,让我们创建一个新的 NextJS 项目(如果您还没有的话)。按照提示操作并设置您的偏好(我不会忽略对 TypeScript 的支持——您也应该这样做😜)。

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

打开代码编辑器并启动开发服务器。

npm run dev
Enter fullscreen mode Exit fullscreen mode

元数据

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

Enter fullscreen mode Exit fullscreen mode

上面的代码片段显示了文件中默认的 RootLayout 组件,它用作全局布局,所有页面和子页面都将从中继承任何 UI - 这也意味着,除非您从单个页面显式导出元数据对象(或) layout.tsx,否则您从该组件导出的元数据对象将由所有其他页面共享page.tsxpage.jsx

  1. 您可以定义并导出名为“使用元数据类型(用于静态详细信息)”的变量metadata,或者定义并导出名为“使用元数据类型(用于静态详细信息)”的函数generateMetadata(此函数对于加载动态数据特别有用,然后可用于创建动态页面的元数据对象)。
  2. 元数据可以在布局文件中定义,并在嵌套页面中被覆盖。
  3. NextJS 会根据您的元数据自动为每个页面生成合适的标签。

一个完整的元数据对象是什么样的?

export const metadata = {
  title: 'My Awesome Website',
  description: 'Discover amazing content and services on My Awesome Website',

  // Basic metadata
  applicationName: 'My Awesome App',
  authors: [{ name: 'Stephen Omoregie', url: 'https://cre8stevedev.me' }],
  generator: 'Next.js',
  keywords: ['next.js', 'react', 'javascript'],
  referrer: 'origin-when-cross-origin',
  themeColor: '#4285f4',
  colorScheme: 'dark',
  viewport: 'width=device-width, initial-scale=1',
  creator: 'Stephen Omoregie',
  publisher: 'Cre8steve Dev',

  // Open Graph metadata
  openGraph: {
    title: 'My Awesome Website',
    description: 'Discover amazing content and services',
    url: 'https://cre8stevedev.me',
    siteName: 'My Awesome Website',
    images: [
      {
        url: 'https://myawesomewebsite.com/og-image.jpg',
        width: 1200, // This is the recommended size in pixels
        height: 630,
        alt: 'My Awesome Website og-image',
      },
    ],
    locale: 'en_US',
    type: 'website',
  },

  // Twitter metadata
  twitter: {
    card: 'summary_large_image',
    title: 'My Awesome Website',
    description: 'Discover amazing content and services',
    creator: '@cre8stevedev',
    images: ['https://myawesomewebsite.com/twitter-image.jpg'],
  },

  // Verification for search engines
  // You can get these values from the respective
  // search engines when you submit your site for 
  // indexing
  verification: {
    google: 'google-site-verification=1234567890',
    yandex: 'yandex-verification=1234567890',
    yahoo: 'yahoo-site-verification=1234567890',
  },

  // Alternate languages
  alternates: {
    canonical: 'https://myawesomewebsite.com',
    languages: {
      'en-US': 'https://myawesomewebsite.com/en-US',
      'es-ES': 'https://myawesomewebsite.com/es-ES',
    },
  },

  // Icons
  icons: {
    icon: '/favicon.ico',
    shortcut: '/favicon-16x16.png',
    apple: '/apple-touch-icon.png',
    other: [
      {
        rel: 'apple-touch-icon-precomposed',
        url: '/apple-touch-icon-precomposed.png',
      },
    ],
  },

  // Manifest
  manifest: '/site.webmanifest',

  // App-specific metadata
  appleWebApp: {
    capable: true,
    title: 'My Awesome App',
    statusBarStyle: 'black-translucent',
  },

  // Robots directives
  robots: {
    index: true,
    follow: true,
    nocache: true,
    googleBot: {
      index: true,
      follow: true,
      noimageindex: true,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },

  // Format detection
  formatDetection: {
    email: false,
    address: false,
    telephone: false,
  },
};
Enter fullscreen mode Exit fullscreen mode

别跑,别跑!不,你不需要在页面上使用所有这些属性。它们只是作为参考,帮助你创建满足自身需求的元数据对象。可以说,许多属性都相当直观易懂。OpenGraph 对象使用 OpenGraph 协议,允许你的应用在社交分享时显示链接、图片和描述的预览。此外,还有一个专门用于 Twitter 元数据的对象属性。

让我们直接进入正题——如何在你的项目中使用它

请记住,如果您有希望嵌套页面共享的静态元数据,只需将元数据对象放在最顶层的组件中(在本例中,该组件是layout.tsx路由结构中的文件)。例如:

my-nextjs-project/
├── app/
│   ├── (auth)/
│   │   ├── signin/
│   │   │   └── page.tsx
│   │   ├── signup/
│   │   │   └── page.tsx
│   │   └── layout.tsx
│   │
│   ├── about/
│   │   └── page.tsx
│   ├── api/
│   │   ├── post/
│   │   │   ├── [slug]/
│   │           └── route.ts
│   ├── layout.tsx
│   ├── robots.ts
│   ├── sitemap.ts
│   └── page.tsx
├── public/
│   ├── favicon.ico
│   └── ...
├── components/
│   └── ...
├── lib/
│   └── customMetaDataGenerator.ts
├── styles/
│   └── globals.css
├── package.json
├── next.config.js
├── tsconfig.json
└── README.md
Enter fullscreen mode Exit fullscreen mode

请注意上面的项目结构,我们将参考它来了解您的文件/配置的位置。

创建可重用的元数据函数

让我们创建一个实用函数,它接收一个属性对象,我们可以向该对象传递自定义值,以便在任何页面上生成元数据。我们将调用这个自定义函数,并将其赋值给名为“metadata”的变量对象,该对象将从页面导出。

// File location: @/lib/customMetaDataGenerator.ts
import { Metadata } from 'next';

interface PageSEOProps {
  title: string;
  description?: string;
  canonicalUrl?: string;
  ogType?: string;
  ogImage?: string;
  twitterCard?: string;
  keywords?: string[];
}

export function customMetaDataGenerator({
  title,
  description = "Join the vibrant community that's bringing Nigerians together like never before...",
  canonicalUrl = 'https://naijarium.vercel.app',
  ogType = 'website',
  keywords = [
    "an array", "of default", "keywords"
  ],
  ogImage = 'https://url-to-your-image-this-is-a-default-value-for-optional-parameter',
  twitterCard = 'summary_large_image',
}: PageSEOProps): Metadata {

  // Create Site Title
  const siteTitle = 'Your Website Name';
  const fullTitle = `${title} | ${siteTitle}`;

  return {
    title: fullTitle,
    description,
    keywords: keywords.join(', '),
    openGraph: {
      title: fullTitle,
      description,
      type: ogType,
      url: canonicalUrl,
      images: [
        {
          url: ogImage,
        },
      ],
    },
    twitter: {
      card: twitterCard,
      title: fullTitle,
      description,
      images: [ogImage],
    },
    alternates: {
      canonical: canonicalUrl,
    },
  };
}

Enter fullscreen mode Exit fullscreen mode

在根布局中使用自定义函数

// @/app/layout.tsx 
import { customMetaDataGenerator } from '@/lib/customMetaDataGenerator';

// Define Metadata for the general site layout
// We're relying on the default parameters defined in the function, 
// That's why we're only passing `title` in the object
export const metadata: Metadata = customMetaDataGenerator({
  title: 'Social Media Forum for Nigerians',
});

...
// The rest of your layout.tsx code follows.

Enter fullscreen mode Exit fullscreen mode

你也可以在所有 page.tsx 文件中针对各个路由执行此操作——如果你有静态数据,例如包含静态数据的多页面网站,这将非常有用。但如果你要将其应用于动态网站(例如包含不同项目的作品集、博客、电子商务网站等),该怎么办呢?那么你需要能够生成特定于请求资源的数据,并在服务器端构建元数据。

生成动态元数据

请注意,要让搜索引擎能够使用元数据,就必须在服务器端运行。因此,如果您的page.tsx客户端组件使用了“use client”指令,那么次优方案是将元数据生成函数放在layout.tsx该路由段中,这样就可以在服务器端生成元数据并返回给客户端组件。

例如,当你generateMetadata在 `<directory> page.tsx` 或`<directory>` 标签中导出名为 `<function>` 的函数时,例如,当一个动态路由接受一个`<params>` 作为参数时,NextJS 会在服务器端自动调用该函数,并且该函数还可以访问该路由的参数。因此,你可以使用 `<slug>` 来获取动态资源的数据,并构建将要返回给该页面的元数据对象。layout.tsxslug

以下是创建 URL 动态元数据的示例:
https://naijarium.vercel.app/post/how-to-become-a-full-stack-dev

// @/app/post/[slug]/layout.tsx
import { customMetaDataGenerator } from '@/lib/customMetaDataGenerator';

import { Metadata } from 'next';
import { fetchSinglePost } from '@/lib/fetchSinglePost';

type Props = {
  params: { slug: string };
  children: React.ReactNode;
};

// This function will be called and it will generate 
// The metadata object for the page when the route is visited

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  // Fetch the post data using the slug
  // Implement your own custom data fetch logic
  // That returns the resource
  const post = await fetchSinglePost(params.slug);

  if (!post) {
    return customMetaDataGenerator({
      title: 'Post Not Found | Naijarium',
    });
  }

  // Generate the metadata using the fetched post data
  return customMetaDataGenerator({
    title: post.title!,
    description: ` Created by: ${post.author_username} - ${post.content).slice(0, 150)} + "...Read More`,
    ogImage: post.image,
    keywords: post.keywords,
    canonicalUrl: `https://your-website.com/post/${post.slug}`
  });
}

// Export the layout component that returns the nested page (s)
export default function Layout({ children }: Props) {
  return <>{children}</>;
}

Enter fullscreen mode Exit fullscreen mode

机器人程序和站点地图

网站元数据非常重要,对很多方面都很有帮助。但如果您希望搜索引擎能够索引和抓取您的网页,那么您还需要两个重要的文件——robots.txt 和 sitemap.xml。

robots.ts

  • 生成 robot.txt 文件
  • 控制搜索引擎爬虫
  • 定义哪些页面应该被索引,哪些页面不应该被索引。
  • 可以屏蔽特定的网络爬虫,也可以允许所有爬虫。

sitemap.ts

  • 生成 sitemap.xml 文件
  • 列出您网站上的所有重要页面
  • 帮助搜索引擎理解您的网站结构
  • 可以包含页面更新信息、重要性和更新频率。

它的妙处在于,如果您将 robots.txt 和 sitemap.xml 文件放在应用程序目录中,NextJS 会自动处理它们的创建robots.tssitemap.ts从而无需手动管理即可提高网站的 SEO。

示例robots.ts文件:

import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*', // allow all crawlers
      allow: '/', // allow crawling all pages
      disallow: ['/api/', '/api'], // don't crawl api routes
    },
    sitemap: 'https://your-website.com/sitemap.xml',
  };
}

Enter fullscreen mode Exit fullscreen mode

示例sitemap.ts文件

import getAllPosts from '@/actions/getAllPosts';
import { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://your-website.com';
  const posts = await getAllPosts();

  const postEntries = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt!),
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }));

  return [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: 'daily',
      priority: 1,
    },
    ...postEntries,
  ];
}

Enter fullscreen mode Exit fullscreen mode

这是怎么回事?现在,您可以手动列出网站中想要添加到站点地图的页面,但如果您有一个动态网站(例如博客或电子商务网站),您可能需要根据您拥有的资源生成页面链接。

您只需返回一个包含站点地图类型的数组。搜索引擎将使用此信息来了解您的网站/应用程序的结构。

最后一步,只剩一点点了。

next-sitemap软件包是一个用于 Next.js 应用程序的工具,它具有以下功能:

  1. 自动生成站点地图和 robots.txt 文件
  2. 支持静态和动态站点地图
  3. 允许自定义配置
  4. 可以为大型应用程序生成站点地图
  5. 可以集成到构建过程中

如何使用?

next-sitemap.config.ts在项目根目录下创建一个文件,并将以下内容添加到该文件中。

module.exports = {
  siteUrl: 'https://your-website-url.com',
  generateRobotsTxt: true,
};
Enter fullscreen mode Exit fullscreen mode

更新脚本对象package.json以包含

{
  "name": "your-project-name",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "postbuild": "next-sitemap"
  },
//  ...other configurations here
}

Enter fullscreen mode Exit fullscreen mode

总结一下,呃……再见!

呼!现在一切就绪,您可以在开发者工具中检查您的网站,或者分享您网站的链接,以查看链接预览是否已生效(当然是在部署之后,而不是本地主机链接)。

尽情享受开发过程吧!当你需要在 NextJS 应用中实现 SEO 时,随时可以参考本指南。

注意:除了页面加载速度、响应速度和内容相关性之外,还有许多其他因素会影响页面排名。但至少 NextJS 让你能够利用 ReactJS 组件构建 UI 的灵活性,同时又不影响搜索引擎对网站的抓取能力。

祝您编程愉快!史蒂夫向您致以问候!

其他资源:

  1. 如何立即将您的网址提交给谷歌
  2. NextJS - 搜索引擎优化课程
  3. NextJS 元数据文档
文章来源:https://dev.to/cre8stevedev/practical-guide-to-implementing-functioning-seo-in-nextjs-app-router-static-dynamic-metadata-4ae2