搜索引擎优化

    10.Next的Seo实践

    会员专享 · 非会员仅可阅读 30% 的正文。

    发布时间
    February 16, 2025
    阅读时间
    10 min read
    作者
    Felix
    访问
    会员专享
    这是预览内容

    非会员仅可阅读 30% 的正文。

    1. Meta标签

    Next App Router比较主流的有两种定义源数据标签的方式,一种是通过在布局或者页面上导出一个 metadata 的对象,会自动生成对应的Meta源数据标签,这是静态的。

    而另外一种则是动态生成meta标签,这种场景通常需要先请求接口得到一些信息的动态源数据页面,在这种情况下我们采用generateMetadata函数。

    1.1. 静态Meta标签

    仅仅只需要在页面或者布局中添加这一段。

    export const metadata: Metadata = {
      metadataBase: new URL(APP_ORIGIN),
      title: APP_TITLE,
      description: APP_DESCRIPTION,
      creator: APP_NAME,
      icons: {
        icon: '/favicon.ico',
        shortcut: '/favicon.ico'
      },
      openGraph: {
        title: APP_TITLE,
        description: APP_DESCRIPTION,
        url: APP_ORIGIN,
        siteName: APP_NAME,
        images: [
          {
            url: OG_URL,
            width: 2880,
            height: 1800,
            alt: APP_NAME
          }
        ],
        type: 'website',
        locale: 'en_US'
      },
      twitter: {
        card: 'summary_large_image',
        site: TWITTER_SOCIAL_URL,
        title: APP_TITLE,
        description: APP_DESCRIPTION,
        images: {
          url: '/og.jpg',
          width: 2880,
          height: 1800,
          alt: APP_NAME
        }
      }
    }

    1.2. 生成的HTML Meta标签

    上面的 metadata 对象会被 Next.js 自动转换为相应的 HTML meta 标签。假设我们的应用配置如下:

    const APP_ORIGIN = 'https://example.com'
    const APP_TITLE = 'My Awesome App'
    const APP_DESCRIPTION = 'This is an awesome app built with Next.js'
    const APP_NAME = 'AwesomeApp'
    const OG_URL = 'https://example.com/og-image.jpg'
    const TWITTER_SOCIAL_URL = '@awesome_app'

    那么,生成的 HTML head 部分可能会包含以下 meta 标签:

    <head>
      <title>My Awesome App</title>
      <meta name="description" content="This is an awesome app built with Next.js" />
      <meta name="creator" content="AwesomeApp" />
      <link rel="icon" href="/favicon.ico" />
      <link rel="shortcut icon" href="/favicon.ico" />
    
      <!-- Open Graph tags -->
      <meta property="og:title" content="My Awesome App" />
      <meta property="og:description" content="This is an awesome app built with Next.js" />
      <meta property="og:url" content="https://example.com" />
      <meta property="og:site_name" content="AwesomeApp" />
      <meta property="og:image" content="https://example.com/og-image.jpg" />
      <meta property="og:image:width" content="2880" />
      <meta property="og:image:height" content="1800" />
      <meta property="og:image:alt" content="AwesomeApp" />
      <meta property="og:type" content="website" />
      <meta property="og:locale" content="en_US" />
    
      <!-- Twitter Card tags -->
      <meta name="twitter:card" content="summary_large_image" />
      <meta name="twitter:site" content="@awesome_app" />
      <meta name="twitter:title" content="My Awesome App" />
      <meta name="twitter:description" content="This is an awesome app built with Next.js" />
      <meta name="twitter:image" content="https://example.com/og.jpg" />
      <meta name="twitter:image:width" content="2880" />
      <meta name="twitter:image:height" content="1800" />
      <meta name="twitter:image:alt" content="AwesomeApp" />
    </head>

    这些生成的 meta 标签包含了我们在 metadata 对象中定义的所有信息,包括基本的页面信息、Open Graph 标签和 Twitter Card 标签。这些标签可以极大地提升我们的网页在搜索引擎结果中的展示效果,以及在社交媒体平台上的分享效果。

    1.3. 动态Meta标签

    对于需要根据动态数据生成元数据的页面,我们可以使用generateMetadata函数。这种方法特别适用于博客文章、产品详情页面等内容随时间或用户输入变化的场景。

    示例代码:

    import type { Metadata } from 'next'
    
    type Props = {
      params: { id: string }
    }
    
    export async function generateMetadata({ params }: Props): Promise<Metadata> {
      // 从API获取数据
      const product = await fetch(`https://api.acme.com/products/${params.id}`).then((res) => res.json())
    
      return {
        title: product.name,
        description: product.description,
        openGraph: {
          title: `${product.name} - Acme Products`,
          description: product.description,
          images: [{ url: product.image }]
        }
      }
    }
    
    export default function Page({ params }: Props) {
      // ...
    }

    这个函数允许我们基于动态数据(如API响应)生成元数据,确保每个页面都有独特且相关的SEO信息。

    1.4 generateMetadata的流式渲染

    流式渲染指的就是不用等待整个ssr中的请求完毕再抛出document,通过 Transfer-Encoding: chunked 的请求头标识把整个document文档进行分块传输,来进行优化页面内容传输以及提升用户体验。

    generateMetadata 函数不会触发 Suspense,我的猜测这是由于其设计和实现方式导致的。以下是几个主要原因:

    1. 服务器端执行generateMetadata 主要在服务器端执行,而 Suspense 主要用于客户端渲染中处理异步操作。

    2. 元数据的关键性:元数据对于SEO非常重要,Next优先考虑确保元数据在初始 HTML 中可用,而不是延迟加载。

    3. 渲染顺序:元数据通常需要在页面内容之前生成,因为它们位于 HTML 的 <head> 部分。这使得难以将其纳入 Suspense 的流式渲染模型中。

    4. 兼容性考虑:不是所有的客户端(如搜索引擎爬虫)都能处理通过 JavaScript 动态插入的元数据。

    这种设计导致了一些潜在的性能问题:

    • 阻塞渲染:如果 generateMetadata 函数执行时间较长,它会延迟整个页面的渲染。
    • 无法并行加载:元数据生成和页面内容加载无法并行进行,可能会增加总体加载时间。
    • 客户端导航延迟:在客户端导航时,新页面的渲染可能会因为等待元数据生成而被延迟。

    为了解决这些问题,Next引入了"流式元数据"(Streaming Metadata)功能。这个新特性旨在提高页面加载速度,特别是在处理慢速元数据生成时,但只能在canary中使用。

    流式元数据的主要优势:

    1. 非阻塞渲染generateMetadata 返回的元数据被视为可挂起的数据,允许页面内容立即渲染。
    2. 异步注入:元数据在解析完成后,会在客户端异步注入到页面中。
    3. SEO友好:对于搜索引擎爬虫,仍然会在HTML中接收完全渲染的元数据。
    4. 用户体验优先:对于人类用户,他们主要关心页面内容,元数据可以稍后添加而不影响他们的体验。

    如何使用:

    要启用流式元数据功能,你需要在 next.config.js 中添加以下配置:

    module.exports = {
      experimental: {
        streamingMetadata: true
      }
    }

    注意事项:

    1. 这个功能默认是禁用的,需要手动开启。
    2. 对于某些有限的机器人(如不能处理JavaScript的爬虫),你可以使用 experimental.htmlLimitedBots 选项来指定它们应该接收完全阻塞的元数据,但我目前的做法是用正则匹配了市面主流的所有爬虫。
    3. 默认情况下,只有能够像无头浏览器一样运行的Google机器人会在启用此功能时接收流式元数据。

    为什么这个解决方案很重要:

    1. 性能提升:通过允许页面内容先渲染,然后异步加载元数据,可以显著提高感知加载速度。
    2. 更好的用户体验:用户可以更快地看到和交互页面内容,而不必等待所有元数据加载完成。
    3. SEO和用户体验的平衡:通过为搜索引擎爬虫提供完整的元数据,同时为人类用户优化加载速度,实现了SEO和用户体验的完美平衡。

    1.5 generateMetadata和页面组件的请求优化

    在使用 generateMetadata 和页面组件时,一个常见的担忧是可能会导致重复的数据请求。因为 generateMetadata 和页面组件可能需要相同的数据。

    请求重复问题

    考虑以下场景:

    import type { Metadata } from 'next'
    
    async function getData(id: string) {
      const res = await fetch(`https://api.example.com/product/${id}`)
      return res.json()
    }
    
    export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
      const product = await getData(params.id)
      return { title: product.name }
    }
    
    export default async function Page({ params }: { params: { id: string } }) {
      const product = await getData(params.id)
      return <h1>{product.name}</h1>
    }

    乍看之下,似乎 getData 函数会被调用两次:一次在 generateMetadata 中,另一次在页面组件中。

    Next.js 的请求去重优化

    但Next.js 已经内置了请求去重优化。在同一个路由段(route segment)内,具有相同参数的重复请求会被自动去重。这意味着:

    1. getData 函数实际上只会被调用一次。
    2. 第一次调用(通常是在 generateMetadata 中)的结果会被缓存。
    3. 后续的调用(在页面组件中)会直接使用缓存的结果,而不会触发新的网络请求。

    2. robots.txt

    2.1. robots.txt 的重要性和基本概念

    robots.txt 文件是网站与搜索引擎爬虫之间的一种通信机制。它位于网站的根目录,作为网站管理员向搜索引擎爬虫传达爬取指令的第一道关卡。正确配置 robots.txt 可以:

    1. 指导爬虫如何爬取网站内容
    2. 防止敏感或不必要的页面被索引
    3. 优化网站的爬取效率
    4. 间接影响网站的 SEO 表现

    在 Next.js 应用中,我们有两种方式来实现 robots.txt:静态文件方法和动态生成方法。每种方法都有其特定的使用场景和优势。

    2.2. 静态Robots.txt

    静态文件方法是最直观的实现方式。你只需在public/目录下创建一个名为 robots.txt 的文件。

    例如,一个基本的 robots.txt 文件可能如下所示:

    User-Agent: *
    Allow: /
    Disallow: /admin/
    Disallow: /private/
    Sitemap: https://www.yourwebsite.com/sitemap.xml

    让我们逐行解析这个文件:

    • User-Agent: *:这一行表示以下规则适用于所有的搜索引擎爬虫。
    • Allow: /:允许爬虫访问网站的所有页面(除非被后续规则覆盖)。
    • Disallow: /admin/:禁止爬虫访问 /admin/ 目录及其子目录。
    • Disallow: /private/:同样禁止爬虫访问 /private/ 目录及其子目录。
    • Sitemap: https://www.yourwebsite.com/sitemap.xml:指明网站 Sitemap 的位置,帮助搜索引擎更好地了解网站结构。

    静态文件方法的优点是简单直接,适合网站结构相对固定、不需要频繁更新 robots.txt 内容的情况。

    2.3. 动态生成

    Next.js

    会员专享

    订阅后解锁完整文章

    支持创作、解锁全文,未来更新也会第一时间送达。

    评论

    加入讨论

    0 条评论
    登录后评论

    还没有评论,来占个沙发吧。