Jak zacházet s metadaty SEO v Next.js

Jak vytvořit vlastní komponentu React pro vykreslování značek metadat SEO a jaké značky zahrnout, abyste zlepšili hodnocení a výkon na webu.

Začínáme

Pro tento tutoriál použijeme CheatCode Next.js Boilerplate jako výchozí bod pro naši práci. Pro začátek naklonujme kopii z Github:

Terminál

git clone https://github.com/cheatcode/nextjs-boilerplate

Dále cd do projektu a nainstalujte jeho závislosti:

Terminál

cd nextjs-boilerplate && npm install

Nakonec pokračujte a spusťte vývojový server:

Terminál

npm run dev

Díky tomu jsme připraveni začít.

Vytvoření SEO komponenty

Pro tento tutoriál vytvoříme komponentu React, která nám pomůže vykreslit naše SEO data v Next.js. To nám usnadní přidávání SEO metadat na jakoukoli stránku, stačí pouze změnit rekvizity, které předáme komponentě.

/components/SEO/index.js

import React from "react";
import PropTypes from "prop-types";
import Head from "next/head";
import settings from "../../settings";

const SEO = (props) => {
  const { title, description, image } = props;
  return (
    <Head>
      <title>{title} | App</title>
      <meta name="description" content={description} />
      <meta itemprop="name" content={title} />
      <meta itemprop="description" content={description} />
      <meta itemprop="image" content={image} />
    </Head>
  );
};

SEO.defaultProps = {
  title: settings && settings.meta && settings.meta.title,
  description: settings && settings.meta && settings.meta.description,
  image:
    settings &&
    settings.meta &&
    settings.meta.social &&
    settings.meta.social.graphic,
};

SEO.propTypes = {
  title: PropTypes.string,
  description: PropTypes.string,
  image: PropTypes.string,
};

export default SEO;

Zaměříme-li se na horní část souboru, pro naši komponentu React použijeme vzor komponenty funkce, protože pro tuto komponentu nepotřebujeme přístup k žádným metodám životního cyklu nebo stavu.

V horní části souboru importujeme další komponentu React z next/head balíček, <Head /> . Toto je komponenta, která je integrována do Next.js a pomáhá nám definovat data, která se budou zobrazovat (automaticky pomocí Next.js) v <head></head> tagy HTML pro stránku, kde je tato komponenta vykreslena.

Pro návratovou hodnotu naše komponenta — <SEO /> —vykreslíme tento <Head /> komponentu jako značku pro otevření a zavření. To pro React znamená, že obsah mezi tagem open a close jsou potomky této komponenty. Ačkoli to nevidíme, React má standardní podporu children kterému je přiřazeno označení předané mezi otevřenou a zavřenou značkou, jak vidíme výše. Interně <Head /> komponenta přečte tento children prop a používá jej k naplnění <head></head> tag ve vykresleném HTML pro stránku (co se odešle zpět do Googlu a dalších vyhledávačů).

Mezi těmito značkami předáváme standardní HTML <title /> tag spolu se sérií <meta /> značky. I když jsme v komponentě React, toto označení představuje prosté HTML. Pokud bychom měli zkopírovat tyto značky a vložit je do <head></head> z prostého .html soubor, prohlížeč by je vykreslil bez problémů.

Zde, protože jsme uvnitř Reactu – nebo přesněji JSX, značkovacího jazyka, který React používá – můžeme předat dynamické hodnoty (zde známé jako výraz React) do atributů těchto značek pomocí složených závorek. Ve výše uvedeném kódu, přímo v těle funkce pro naši komponentu, používáme destrukci objektů JavaScriptu k „odstranění“ title , description a image očekáváme, že budou předány našemu <SEO /> komponenta.

Za předpokladu, že jsou definovány, nastavíme je v našem označení metadat pomocí výrazů React, čímž se vykreslí title v <title></title> tag a ostatní jako content atribut jejich příslušných <meta /> značky. Zde je důležité poznamenat:protože se v zájmu SEO snažíme pokrýt všechny naše základy, budeme svědky toho, že se data předávají různým značkám vícekrát. Toto je záměrné. Je to proto, že různé vyhledávače analyzují naše data různými způsoby. Tímto způsobem zajišťujeme maximální kompatibilitu našeho obsahu.

V dolní části našeho souboru si všimneme, že využíváme .defaultProps Reactu a .propTypes atributy pro naši komponentu. Ten druhý, .propTypes , má za cíl pomoci nám ověřit obsah rekvizit, které nám byly předány. To je pro nás jako vývojáře a neovlivňuje to naše uživatele. Zde pomocí PropTypes objekt, který jsme importovali nahoře, jsme nastavili očekávání našich tří rekvizit title , description a image všechny obsahují řetězcovou hodnotu (v našem kódu označenou jako PropTypes.string ).

Těsně nad tím také definujeme nějaký defaultProps pro naše <SEO /> komponent. Toto je důležité. Všimněte si, že zde přistupujeme k hodnotě settings předpokládáme, že jde o objekt importovaný odjinud v našem projektu. V základní verzi, kterou jsme naklonovali na začátku tutoriálu, existuje konvence pro načítání souboru libovolných nastavení na základě aktuálního prostředí nebo hodnoty process.env.NODE_ENV . Ve výchozím nastavení je tato hodnota development a tak očekáváme, že základní deska načetla obsah /settings/settings-development.js soubor pro nás.

/settings/settings-development.js

const settings = {
  graphql: {
    uri: "http://localhost:5001/api/graphql",
  },
  meta: {
    rootUrl: "http://localhost:5000",
    title: "App",
    description: "The app description goes here.",
    social: {
      graphic:
        "https://cheatcode-assets.s3.amazonaws.com/default-social-graphic.png",
      twitter: "@cheatcodetuts",
    },
  },
  routes: {
    authenticated: {
      pathAfterFailure: "/login",
    },
    public: {
      pathAfterFailure: "/documents",
    },
  },
};

export default settings;

Všimněte si, že v těchto nastaveních uvidíme meta objekt je nastaven na sérii párů klíč/hodnota. Tato data jsou nastavena jako výchozí metadata SEO pro celý náš web (nebo, řečeno jiným způsobem, záložní data, na která se budeme spoléhat, pokud nepředáme žádné hodnoty pro rekvizity našeho <SEO /> komponenta).

/components/SEO/index.js

import React from "react";
import PropTypes from "prop-types";
import Head from "next/head";
import settings from "../../settings";

const SEO = (props) => {
  const { title, description, image } = props;
  return (
    <Head>
      <title>{title} | App</title>
      <meta name="description" content={description} />
      <meta itemprop="name" content={title} />
      <meta itemprop="description" content={description} />
      <meta itemprop="image" content={image} />
    </Head>
  );
};

SEO.defaultProps = {
  title: settings && settings.meta && settings.meta.title,
  description: settings && settings.meta && settings.meta.description,
  image:
    settings &&
    settings.meta &&
    settings.meta.social &&
    settings.meta.social.graphic,
};

SEO.propTypes = {
  title: PropTypes.string,
  description: PropTypes.string,
  image: PropTypes.string,
};

export default SEO;

Zpět v naší komponentě vidíme, že vtahujeme tento soubor nastavení a náš .defaultProps objekt, který předá obsah meta objekt v tomto souboru. Opět to zajistí, že pokud to uděláme ne předejte tyto rekvizity, budeme mít nějaké data předaná jako protiklad k prázdnému řetězci nebo "nedefinované" hodnotě.

Přidání značek metadat pro sociální média

Zatímco kód, na který jsme se podívali výše, nám jistě pomůže začít s našimi potřebami SEO, v moderních webových podmínkách by to bylo jako přinést nůž do přestřelky. Protože se web rozšířil do různých sociálních sítí a stále složitějších algoritmů vyhledávačů, pomáhá našemu případu kvůli hodnocení přidat konkrétnější údaje.

Konkrétně chceme přidat podporu pro sociální data ze dvou velkých webů:Twitter a Facebook. Naštěstí, i když budeme muset podporovat více značek, struktura těchto značek je dostatečně podobná, že můžeme automatizovat většinu jejich výstupu.

/components/SEO/index.js

import React from "react";
import PropTypes from "prop-types";
import Head from "next/head";
import settings from "../../settings";

const socialTags = ({
  openGraphType,
  url,
  title,
  description,
  image,
  createdAt,
  updatedAt,
}) => {
  const metaTags = [
    { name: "twitter:card", content: "summary_large_image" },
    {
      name: "twitter:site",
      content:
        settings &&
        settings.meta &&
        settings.meta.social &&
        settings.meta.social.twitter,
    },
    { name: "twitter:title", content: title },
    { name: "twitter:description", content: description },
    {
      name: "twitter:creator",
      content:
        settings &&
        settings.meta &&
        settings.meta.social &&
        settings.meta.social.twitter,
    },
    { name: "twitter:image:src", content: image },
    { name: "twitter:card", content: "summary_large_image" },
    { name: "og:title", content: title },
    { name: "og:type", content: openGraphType },
    { name: "og:url", content: url },
    { name: "og:image", content: image },
    { name: "og:description", content: description },
    {
      name: "og:site_name",
      content: settings && settings.meta && settings.meta.title,
    },
    {
      name: "og:published_time",
      content: createdAt || new Date().toISOString(),
    },
    {
      name: "og:modified_time",
      content: updatedAt || new Date().toISOString(),
    },
  ];

  return metaTags;
};

const SEO = (props) => {
  const { url, title, description, image } = props;

  return (
    <Head>
      <title>{title} | App</title>
      <meta name="description" content={description} />
      <meta itemprop="name" content={title} />
      <meta itemprop="description" content={description} />
      <meta itemprop="image" content={image} />
      {socialTags(props).map(({ name, content }) => {
        return <meta key={name} name={name} content={content} />;
      })}
    </Head>
  );
};

SEO.defaultProps = {
  url: "/",
  openGraphType: "website",
  ...
};

SEO.propTypes = {
  url: PropTypes.string,
  openGraphType: PropTypes.string,
  ...
};

export default SEO;

Než se pustíme do sociálních značek, opravdu rychle, chceme upozornit na url a openGraphType pole, která jsme přidali do našeho propTypes a defaultProps . Ty představují url stránky, na které se právě nacházíme (např. pokud jsme na blogu jako /posts/the-slug-of-the-post ) a openGraphType který se bude mapovat na hodnotu typu z definice typů objektů protokolu Open Graph.

Část, kterou jsme skutečně péče o zde je v naší návratové hodnotě:nové .map() děláme.

Zde jsme zavedli funkci nahoře, která vrací pole objektů, přičemž každý objekt obsahuje hodnotu pro name a content atribut na <meta /> štítek. Všimněte si, že názvy se mění podle konkrétní sociální sítě, ale struktura ne . Toto je záměrné.

Zatímco Twitter a Facebook (og zde znamená „Otevřený graf“, což je standard vytvořený Facebookem) mají své vlastní jedinečné názvy metadat, oba používají stejný mechanismus ke sdílení těchto dat. V našem kódu toho můžeme využít a zacyklit se přes pole objektů, z nichž každý vyplivne <meta /> tag předáním name a content pro aktuální položku, kterou procházíme jako atributy na značce.

K provedení této smyčky zavoláme socialTags() nejprve předejte props pro naši komponentu a poté dynamicky naplnit pole objektů, které funkce vrací, těmito hodnotami prop. Na oplátku dostaneme zpět pole objektů, které očekáváme v našem return hodnotu.

Tam zřetězujeme volání na .map() na naše volání na číslo socialTags(props) a pro každou položku ve vráceném poli vykreslete <meta /> tag s odpovídajícími atributy pro daný objekt.

Je důležité si uvědomit:to, co vidíte, je jen nějaké dostupných metaznaček pro Twitter a Facebook. V závislosti na vašem vlastním webu možná budete chtít zahrnout méně nebo více značek.

V případě Twitteru se můžete odkázat na jejich dokumentaci ke značení karet a v případě Facebooku na dokumentaci protokolu Open Graph.

Když jsou tyto prvky na místě, nyní, když je náš obsah sdílen na Twitteru nebo Facebooku, získáme správně zobrazený prvek „karty“, který vypadá hezky v časové ose lidí.

Přidávání metadat Google JSON-LD

Než vložíme naše <SEO /> chceme přidat ještě jeden typ metadat:JSON-LD společnosti Google ("LD" znamená "Linking Data"). Toto jsou data, která Google používá pro funkce, jako jsou jejich informační karty ve výsledcích vyhledávání.

/components/SEO/index.js

import React from "react";
import PropTypes from "prop-types";
import Head from "next/head";
import settings from "../../settings";

const socialTags = ({
  openGraphType,
  url,
  title,
  description,
  image,
  createdAt,
  updatedAt,
}) => { ... };

const SEO = (props) => {
  const { url, title, description, image, schemaType } = props;

  return (
    <Head>
      ...
      {socialTags(props).map(({ name, content }) => {
        return <meta key={name} name={name} content={content} />;
      })}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{
          __html: JSON.stringify({
            "@context": "http://schema.org",
            "@type": schemaType,
            name: title,
            about: description,
            url: url,
          }),
        }}
      />
    </Head>
  );
};

SEO.defaultProps = {
  url: "/",
  openGraphType: "website",
  schemaType: "Article",
  ...
};

SEO.propTypes = {
  url: PropTypes.string,
  openGraphType: PropTypes.string,
  schemaType: PropTypes.string,
  ...
};

export default SEO;

Přímo pod našimi sociálními značkami .map() , nyní jsme přidali <script /> tag s type atribut nastaven na application/ld+json (typ MIME, který Google hledá při kontrole dat JSON-LD). Protože data pro JSON-LD se obvykle předávají jako objekt mezi script tags, musíme to "React-ify" to "React-ify", abychom nedostali žádné runtime chyby.

K tomu využíváme React's dangerouslySetInnerHTML prop, předá mu objekt s __html atribut nastaven na stringovanou verzi našeho objektu JSON-LD. Když se to vykreslí, React zde dynamicky nastaví objekt jako vnitřní HTML nebo obsah našeho <script /> tag pro nás (bez ohledu na Google a funguje stejně).

Na objektu používáme strukturu JSON-LD a k popisu našich dat používáme definice typu schema.org.

A je to! Abychom to zakončili, podívejme se, jak naši komponentu používat.

Pomocí naší SEO komponenty

Abychom mohli naši komponentu používat, rychle zapojíme ukázkovou stránku do našeho standardu. Za tímto účelem vytvoříme falešný „příspěvek na blogu“ v souboru s názvem /pages/post/index.js :

/pages/post/index.js

import React from "react";
import PropTypes from "prop-types";
import SEO from "../../components/SEO";
import StyledPost from "./index.css";

const Post = (props) => (
  <StyledPost>
    <SEO
      url={`${props.url}/post`}
      openGraphType="website"
      schemaType="article"
      title="The Fate of Empires"
      description="The only thing we learn from history, it has been said, 'is that men never learn from history'..."
      image={`${props.url}/colosseum.jpeg`}
    />
    <header>
      <h1>The Fate of Empires</h1>
      <h5>Sir John Glubb</h5>
    </header>
    <div>
      <img src="/colosseum.jpeg" alt="Colosseum" />
      <p>
        As we pass through life, we learn by experience. We look back on our
        behaviour when we were young and think how foolish we were. In the same
        way our family, our community and our town endeavour to avoid the
        mistakes made by our predecessors.
      </p>
      <p>
        The experiences of the human race have been recorded, in more or less
        detail, for some four thousand years. If we attempt to study such a
        period of time in as many countries as possible, we seem to discover the
        same patterns constantly repeated under widely differing conditions of
        climate, culture and religion. Surely, we ask ourselves, if we studied
        calmly and impartially the history of human institutions and development
        over these four thousand years, should we not reach conclusions which
        would assist to solve our problems today? For everything that is
        occurring around us has happened again and again before.
      </p>
      <p>
        No such conception ever appears to have entered into the minds of our
        historians. In general, historical teaching in schools is limited to
        this small island. We endlessly mull over the Tudors and the Stewarts,
        the Battle of Crecy, and Guy Fawkes. Perhaps this narrowness is due to
        our examination system, which necessitates the careful definition of a
        syllabus which all children must observe.
      </p>
      <p>
        The only thing we learn from history,’ it has been said, ‘is that men
        never learn from history’, a sweeping generalisation perhaps, but one
        which the chaos in the world today goes far to confirm. What then can be
        the reason why, in a society which claims to probe every problem, the
        bases of history are still so completely unknown?{" "}
      </p>
    </div>
  </StyledPost>
);

Post.propTypes = {
  url: PropTypes.string.isRequired,
};

export const getServerSideProps = (context) => {
  return {
    props: {
      url: context?.req?.headers?.host,
    },
  };
};

export default Post;

Část, na které nám záleží nejvíce, je vykreslení našeho <SEO /> komponent. Všimněte si, že jsme to importovali do horní části našeho souboru a vykreslujeme to přímo uvnitř <StyledPost /> zde komponenta (toto je speciální typ komponenty React známý jako stylovaná komponenta). Takže to máte, opravdu rychle, zde je zdroj pro tuto komponentu (pozor na cestu):

/pages/post/index.css.js

import styled from "styled-components";

export default styled.div`
  max-width: 800px;
  margin: 0 auto;

  header {
    margin: 25px 0;
    padding: 0;
  }

  header h1 {
    font-size: 28px;
    font-weight: bold;
  }

  header h5 {
    color: #888888;
  }

  div img {
    max-width: 100%;
    display: block;
    margin: 0px 0px 25px;
  }

  div p {
    font-size: 18px;
    line-height: 28px;
  }

  @media screen and (min-width: 768px) {
    header {
      margin: 50px 0 50px;
      padding: 0;
    }

    div img {
      max-width: 100%;
      display: block;
      margin: 0px 0px 50px;
    }
  }
`;

Zde používáme styled-components knihovna obsažená ve standardu Next.js, který používáme, aby nám pomohla dynamicky vytvořit komponentu React, která vrací HTML <div /> prvek s CSS předávaným mezi zadními značkami zde jako styly pro že <div /> . Co a proč nejsou pro tento tutoriál příliš důležité, takže po přidání tohoto souboru se vraťme na stránku s příspěvky.

/pages/post/index.js

import React from "react";
import PropTypes from "prop-types";
import SEO from "../../components/SEO";
import StyledPost from "./index.css";

const Post = (props) => (
  <StyledPost>
    <SEO
      url={`${props.url}/post`}
      openGraphType="website"
      schemaType="article"
      title="The Fate of Empires"
      description="The only thing we learn from history, it has been said, 'is that men never learn from history'..."
      image={`${props.url}/colosseum.jpeg`}
    />
    <header>
      <h1>The Fate of Empires</h1>
      <h5>Sir John Glubb</h5>
    </header>
    <div>
      <img src="/colosseum.jpeg" alt="Colosseum" />
      <p>
        As we pass through life, we learn by experience. We look back on our
        behaviour when we were young and think how foolish we were. In the same
        way our family, our community and our town endeavour to avoid the
        mistakes made by our predecessors.
      </p>
      <p>
        The experiences of the human race have been recorded, in more or less
        detail, for some four thousand years. If we attempt to study such a
        period of time in as many countries as possible, we seem to discover the
        same patterns constantly repeated under widely differing conditions of
        climate, culture and religion. Surely, we ask ourselves, if we studied
        calmly and impartially the history of human institutions and development
        over these four thousand years, should we not reach conclusions which
        would assist to solve our problems today? For everything that is
        occurring around us has happened again and again before.
      </p>
      <p>
        No such conception ever appears to have entered into the minds of our
        historians. In general, historical teaching in schools is limited to
        this small island. We endlessly mull over the Tudors and the Stewarts,
        the Battle of Crecy, and Guy Fawkes. Perhaps this narrowness is due to
        our examination system, which necessitates the careful definition of a
        syllabus which all children must observe.
      </p>
      <p>
        The only thing we learn from history,’ it has been said, ‘is that men
        never learn from history’, a sweeping generalisation perhaps, but one
        which the chaos in the world today goes far to confirm. What then can be
        the reason why, in a society which claims to probe every problem, the
        bases of history are still so completely unknown?{" "}
      </p>
    </div>
  </StyledPost>
);

Post.propTypes = {
  url: PropTypes.string.isRequired,
};

export const getServerSideProps = (context) => {
  return {
    props: {
      url: context?.req?.headers?.host,
    },
  };
};

export default Post;

Podívejte se na naše vykreslení <SEO /> Jak jsme naznačili během vývoje komponenty, vše, co děláme, je předávání rekvizit s daty, která chceme mapovat, do našich různých metaznaček uvnitř komponenty. Zatímco zde napevno kódujeme naše ukázkové rekvizity, technicky byste mohli (a pravděpodobně budete) použít výraz React k předání nějaké proměnné hodnoty v závislosti na tom, kde komponentu vykreslíte.

Než to nazveme hotovým, opravdu rychle, chceme upozornit na použití getServerSideProps v dolní části našeho souboru. Toto je funkce, kterou Next.js používá k tomu, jak název napovídá, k získání jakýchkoli rekvizit pro naši komponentu v kontextu serveru před na straně serveru vykresluje naši komponentu. Toto je důležité. Vykreslování na straně serveru je termín používaný k popisu počáteční odpovědi odeslané zpět na požadavek HTTP. Tato odpověď „vykreslí“ nějaké HTML, které žadatel obdrží.

Takto fungují vyhledávače. Stránky jako Google mají „prohledávač“, který navštěvuje všechny veřejné adresy URL na internetu. Hledá tuto počáteční odpověď, aby získal HTML, který používá ke generování výsledků vyhledávání. To je přesně to, kdy očekáváme náš <SEO /> komponenta, která má být vykreslena a "vyzvednuta" vyhledávači.

Zde uvnitř getServerSideProps chceme získat základní adresu URL (aktuální doménu) aplikace a předat ji naší komponentě jako podporu url . Chceme to udělat tak, že když vykreslíme naše <SEO /> komponenta jako součást počáteční HTML odpovědi, URL, kterou předáme pro url podpěra na naší komponentě je správná. Pokud neudělali Pokud to uděláte, první odpověď, kterou pošleme zpět do vyhledávače, bude mít „nedefinovanou“ adresu URL.

S tím jsme připraveni na test. Otevřeme http://localhost:5000/post stránku v našem webovém prohlížeči a zobrazte zdroj naší stránky a zkontrolujte, zda se naše metadata vykreslují podle očekávání:

Skvělý. Protože zde vidíme vykreslená naše metadata, můžeme se spolehnout, že to je to, co Google (nebo jakýkoli jiný vyhledávač uvidí), když jejich prohledávač požádá o náš web.

Zabalení

V tomto tutoriálu jsme se naučili, jak zapojit vlastní <SEO /> komponenta React, která nám pomáhá dynamicky vykreslovat značky metadat na základě rekvizit, které jsme této komponentě předali. Naučili jsme se vykreslovat základní HTML <meta /> tagy, stejně jako tagy nezbytné pro stránky sociálních médií, jako je Twitter a Facebook. Nakonec jsme se naučili, jak přidat Google JSON-LD <script /> do naší komponenty, abychom přidali více kontextu a zlepšili naše šance na umístění ve výsledcích vyhledávání.