SEO

    10.Next’s SEO practice

    Members only · Non-members can read 30% of the article.

    Published
    February 16, 2025
    Reading Time
    10 min read
    Author
    Felix
    Access
    Members only
    Preview only

    Non-members can read 30% of the article.

    1. Meta tag

    Next App Router has two more mainstream ways of defining source data tags. One is to export a metadata object on the layout or page, and the corresponding Meta source data tag will be automatically generated, which is static.

    The other is to dynamically generate the meta tag. In this scenario, you usually need to first request the interface to get some information from the dynamic source data page. In this case, we use the generateMetadata function.

    1.1. Static Meta tag

    Just add this paragraph to the page or layout.

    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. Generated HTML Meta tag

    The above metadata object will be automatically converted into the corresponding HTML meta tag by Next.js. Assume our application configuration is as follows:

    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'

    Then, the generated HTML head section may contain the following meta tags:

    <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>

    These generated meta tags contain all the information we defined in the metadata object, including basic page information, Open Graph tags and Twitter Card tags. These tags can greatly improve the visibility of our pages in search engine results and sharing on social media platforms.

    1.3. Dynamic Meta tags

    For pages that need to generate metadata based on dynamic data, we can use the generateMetadata function. This method is particularly suitable for scenarios where content changes over time or user input, such as blog posts and product detail pages.

    Sample code:

    import type { Metadata } from 'next'
    
    type Props = {
      params: { id: string }
    }
    
    export async function generateMetadata({ params }: Props): Promise<Metadata> {
      // Get data from 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) {
      // ...
    }

    This function allows us to generate metadata based on dynamic data (such as API responses), ensuring that each page has unique and relevant SEO information.

    1.4 Streaming rendering of generateMetadata

    Streaming rendering means that you do not need to wait for the entire request in the SSR to be completed before throwing the document. The entire document document is transmitted in chunks through the Transfer-Encoding: chunked request header to optimize page content transmission and improve user experience.

    The generateMetadata function does not trigger Suspense, my guess is this is due to the way it is designed and implemented. Here are a few of the main reasons:

    1. Server-side execution: generateMetadata is mainly executed on the server side, while Suspense is mainly used to handle asynchronous operations in client-side rendering.

    2. The criticality of metadata: Metadata is very important for SEO, and Next prioritizes ensuring that metadata is available in the initial HTML rather than lazy loading.

    3. Rendering Order: Metadata usually needs to be generated before the page content because they are located in the <head> section of the HTML. This makes it difficult to incorporate into Suspense's streaming rendering model.

    4. Compatibility considerations: Not all clients (such as search engine crawlers) can handle metadata dynamically inserted through JavaScript.

    This design leads to some potential performance issues:

    • Blocking rendering: If the generateMetadata function takes a long time to execute, it will delay the rendering of the entire page.
    • Cannot Load in Parallel: Metadata generation and page content loading cannot occur in parallel, potentially increasing overall load time.
    • Client-Side Navigation Delay: When navigating client-side, the rendering of new pages may be delayed waiting for metadata to be generated.

    In order to solve these problems, Next introduced the "Streaming Metadata" function. This new feature is designed to improve page load speeds, especially when dealing with slow metadata generation, but is only available in canary.

    Main advantages of streaming metadata:

    1. Non-blocking rendering: The metadata returned by generateMetadata is treated as suspendable data, allowing page content to be rendered immediately.
    2. Asynchronous Injection: After the metadata is parsed, it will be asynchronously injected into the page on the client side.
    3. SEO Friendly: For search engine crawlers, fully rendered metadata will still be received in the HTML.
    4. User experience first: For human users, who mainly care about page content, metadata can be added later without affecting their experience.

    How to use:

    To enable streaming metadata functionality, you need to add the following configuration in next.config.js:

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

    Notes:

    1. This function is disabled by default and needs to be enabled manually.
    2. For some limited robots (such as crawlers that cannot handle JavaScript), you can use the experimental.htmlLimitedBots option to specify that they should receive fully blocked metadata, but my current approach is to use regular expressions to match all mainstream crawlers on the market.
    3. By default, only Google bots capable of running like a headless browser will receive streaming metadata when this feature is enabled.

    Why this solution is important:

    1. Performance Improvement: By allowing the page content to be rendered first, and then the metadata to be loaded asynchronously, the perceived loading speed can be significantly improved.
    2. Better User Experience: Users can see and interact with page content faster without having to wait for all metadata to load.
    3. Balance of SEO and user experience: Achieving the perfect balance of SEO and user experience by providing complete metadata to search engine crawlers while optimizing loading speed for human users.

    1.5 GenerateMetadata and request optimization of page components

    A common concern when using generateMetadata with page components is the potential for duplicate data requests. Because generateMetadata and page components may require the same data.

    Request duplicate question

    Consider the following scenario:

    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>
    }

    At first glance, it seems that the getData function is called twice: once in generateMetadata and once in the page component.

    Next.js request deduplication optimization

    But Next.js already has built-in request deduplication optimization. Within the same route segment, duplicate requests with

    Members only

    Subscribe to unlock the full article

    Support the writing, unlock every paragraph, and receive future updates instantly.

    Comments

    Join the conversation

    0 comments
    Sign in to comment

    No comments yet. Be the first to add one.