chapter-three / next-drupal

Next.js for Drupal has everything you need to build a next-generation front-end for your Drupal site: SSG, SSR, and ISR, Multi-site, Authentication, Webforms, Search API, I18n and Preview mode (works with JSON:API and GraphQL).
https://next-drupal.org
MIT License
625 stars 172 forks source link

Documentation for SEO #465

Open KemaneWright opened 1 year ago

KemaneWright commented 1 year ago

Would be awesome if there was some documentation on best practices and methods for implementing SEO features from Drupal into the Next.js site. Like Metatag & XML Sitemap, GA, GTM, etc.

yobottehg commented 1 year ago

Just dumping what we have been doing in a current project. Unsure if this is documentation worthy but perhaps it helps someone:

Metatag

We're basically just exposing what metatag is doing using the patch from this issue https://drupal.org/node/2945817.

Inside the next.js client we're including the computed metatag field in the fields list

Then we have a meta tags component which rebuilds the exposed metatag json in a format we want can use in the client.

Here a dump of the component (Favicons generated using https://realfavicongenerator.net/):

import Head from 'next/head'

interface MetaTagAttributes {
  rel?: string
  name?: string
  href?: string
  content?: string
  property?: string
}

interface MetaTag {
  tag: 'link' | 'meta' | 'title'
  key?: string
  attributes: MetaTagAttributes
}

interface MetaProps {
  MetaTags: MetaTag[]
  title: string
}

export function Meta({ MetaTags, title }: MetaProps) {
  return (
    <Head>
      <title>{title}</title>
      <link
        rel="apple-touch-icon"
        sizes="180x180"
        href="/favicons/apple-touch-icon.png"
      />
      <link
        rel="icon"
        type="image/png"
        sizes="32x32"
        href="/favicons/favicon-32x32.png"
      />
      <link
        rel="icon"
        type="image/png"
        sizes="16x16"
        href="/favicons/favicon-16x16.png"
      />
      <link rel="manifest" href="/favicons/site.webmanifest" />
      <link
        rel="mask-icon"
        href="/favicons/safari-pinned-tab.svg"
        color="#000066"
      />
      <meta name="msapplication-TileColor" content="#010D7F" />
      <meta name="theme-color" content="#010D7F" />
      {MetaTags.map((metaTag) => {
        metaTag.attributes = absoluteClientPaths(metaTag.attributes)
        metaTag.key = getMetaTagKey(metaTag.attributes)
        return <metaTag.tag {...metaTag.attributes} key={metaTag.key} />
      })}
    </Head>
  )
}

function absoluteClientPaths(attributes: MetaTagAttributes): MetaTagAttributes {
  // Handle images with next image
  if (
    attributes.rel === 'image_src' &&
    attributes.href.startsWith(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL)
  ) {
    attributes.href = `${process.env.NEXT_PUBLIC_BASE_URL}/_next/image?url=${attributes.href}&w=1920&q=75`
  }

  if (
    attributes.property === 'og:image' &&
    attributes.content.startsWith(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL)
  ) {
    attributes.href = `${process.env.NEXT_PUBLIC_BASE_URL}/_next/image?url=${attributes.content}&w=1920&q=75`
  }

  // Handle absolute urls to the cms
  if (attributes.href?.startsWith(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL)) {
    attributes.href = attributes.href?.replace(
      process.env.NEXT_PUBLIC_DRUPAL_BASE_URL,
      process.env.NEXT_PUBLIC_BASE_URL,
    )
  }

  if (attributes.content?.startsWith(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL)) {
    attributes.content = attributes.content?.replace(
      process.env.NEXT_PUBLIC_DRUPAL_BASE_URL,
      process.env.NEXT_PUBLIC_BASE_URL,
    )
  }

  return attributes
}

function getMetaTagKey(attributes: MetaTagAttributes): string {
  return attributes.rel ?? attributes.name ?? attributes.property
}

Sitemap

We investigated options for achieving quite the same like Drupal is doing but failed. So we just use the Drupal sitemap. For that we override the URL from the simple_sitemap module $config['simple_sitemap.settings']['base_url'] = $clientUrl; and then add a rewrite to the client nextjs config:

...
async rewrites() {
    return [
      ...
      {
        source: '/sitemap.xml',
        destination: process.env.NEXT_PUBLIC_DRUPAL_BASE_URL + '/sitemap.xml',
      },
    ]
  },
...

GA and GTM are just injected into the client, without any connection to Drupal.