Cómo manejar los metadatos de SEO en Next.js

Cómo crear un componente de React personalizado para representar etiquetas de metadatos de SEO y qué etiquetas incluir para mejorar la clasificación y el rendimiento en la web.

Primeros pasos

Para este tutorial, vamos a utilizar CheatCode Next.js Boilerplate como punto de partida para nuestro trabajo. Para empezar, clonemos una copia de Github:

Terminal

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

A continuación, cd en el proyecto e instalar sus dependencias:

Terminal

cd nextjs-boilerplate && npm install

Finalmente, continúe e inicie el servidor de desarrollo:

Terminal

npm run dev

Con eso, estamos listos para comenzar.

Creación de un componente SEO

Para este tutorial, vamos a crear un componente React que nos ayudará a representar nuestros datos de SEO dentro de Next.js. Esto nos facilitará agregar metadatos de SEO a cualquier página, solo teniendo que cambiar los accesorios que le pasamos al componente.

/componentes/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;

Centrándonos cerca de la parte superior del archivo, para nuestro componente React, vamos a usar el patrón del componente de función ya que no necesitamos acceso a ningún método de ciclo de vida o estado para este componente.

En la parte superior del archivo, importamos otro componente React del next/head paquete, <Head /> . Este es un componente que está integrado en Next.js y nos ayuda a definir los datos que se mostrarán (automáticamente por Next.js) en el <head></head> etiquetas del HTML para la página donde se representa este componente.

Para el valor de retorno de nuestro componente—<SEO /> —representamos este <Head /> componente como una etiqueta de apertura y cierre. Esto significa para React que el contenido entre la etiqueta de apertura y cierre son elementos secundarios de ese componente. Aunque no podemos verlo, React tiene un accesorio estándar children que se le asigna el marcado pasado entre una etiqueta de apertura y cierre como vemos arriba. Internamente, el <Head /> componente lee este children prop y lo usa para llenar el <head></head> etiqueta en el HTML representado para la página (lo que se envía de vuelta a Google y otros motores de búsqueda).

Entre esas etiquetas, pasamos un HTML estándar <title /> etiqueta junto con una serie de <meta /> etiquetas Aunque estamos en un componente React, este marcado representa HTML simple. Si tuviéramos que copiar estas etiquetas y pegarlas en el <head></head> de un simple .html archivo, el navegador los mostraría sin problema.

Aquí, debido a que estamos dentro de React, o, más correctamente, JSX, el lenguaje de marcado que usa React, podemos pasar valores dinámicos (aquí, conocidos como expresiones de React) a los atributos de estas etiquetas usando llaves. En el código anterior, justo dentro del cuerpo de la función de nuestro componente, estamos usando la desestructuración de objetos de JavaScript para "arrancar" el title , description y image anticipamos ser pasados ​​a nuestro <SEO /> componente.

Suponiendo que estos estén definidos, los establecemos en nuestro marcado de metadatos usando expresiones React, representando el title en el <title></title> etiqueta y los demás como el content atributo de sus respectivos <meta /> etiquetas Es importante tener en cuenta aquí:debido a que estamos tratando de cubrir todas nuestras bases por el bien del SEO, veremos que los datos se pasan varias veces a diferentes etiquetas. Esto es intencional. Esto se debe a que diferentes motores de búsqueda analizarán nuestros datos de diferentes maneras. Al hacer esto, estamos asegurando la máxima compatibilidad para nuestro contenido.

Hacia el final de nuestro archivo, notaremos que estamos aprovechando el .defaultProps de React y .propTypes atributos de nuestro componente. El último, .propTypes , está destinado a ayudarnos a validar el contenido de los accesorios que se nos pasan. Esto es para nosotros como desarrolladores y no afecta a nuestros usuarios. Aquí, usando el PropTypes objeto que hemos importado arriba, establecemos la expectativa de nuestros tres accesorios title , description y image todos contienen un valor de cadena (designado en nuestro código como PropTypes.string ).

Justo encima de esto, también definimos algunos defaultProps para nuestro <SEO /> componente. Esto es importante. Tenga en cuenta que aquí estamos accediendo a un valor settings que asumimos es un objeto importado de otra parte de nuestro proyecto. En el modelo que clonamos al comienzo del tutorial, existe una convención para cargar un archivo de configuraciones arbitrarias basadas en el entorno actual o el valor de process.env.NODE_ENV . Por defecto, este valor es development por lo que esperamos que el repetitivo haya cargado el contenido del /settings/settings-development.js archivo para nosotros.

/configuración/configuración-desarrollo.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;

Cabe destacar que veremos que en esta configuración, un meta object se establece en una serie de pares clave/valor. Estos datos se configuran como los metadatos de SEO predeterminados para todo nuestro sitio (o, dicho de otra manera, los datos de respaldo en los que confiaremos si no pasamos ningún valor para los accesorios de nuestro <SEO /> componente).

/componentes/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;

De vuelta en nuestro componente, podemos ver que estamos extrayendo ese archivo de configuración y, en nuestro .defaultProps objeto, pasando el contenido del meta objeto en ese archivo. Nuevamente, esto asegura que si no pasa estos accesorios, tendremos algunos datos pasados ​​en lugar de una cadena vacía o un valor "indefinido".

Adición de etiquetas de metadatos para redes sociales

Si bien el código que vimos anteriormente sin duda nos ayudará a comenzar con nuestras necesidades de SEO, en términos web modernos, eso sería como llevar un cuchillo a un tiroteo. Debido a que la web ha proliferado en diferentes redes sociales y algoritmos de motores de búsqueda cada vez más complejos, ayuda a nuestro caso en aras de la clasificación para agregar datos más específicos.

En particular, queremos agregar soporte para datos sociales de dos grandes sitios:Twitter y Facebook. Afortunadamente, aunque necesitaremos admitir más etiquetas, la estructura de esas etiquetas es lo suficientemente similar como para que podamos automatizar la mayor parte de su salida.

/componentes/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;

Antes de profundizar en las etiquetas sociales, muy rápido, queremos llamar la atención sobre el url y openGraphType campos que hemos agregado a nuestro propTypes y defaultProps . Estos representan el url de la página en la que nos encontramos actualmente (por ejemplo, si estamos en una publicación de blog como /posts/the-slug-of-the-post ) y un openGraphType que se asignará a un valor de tipo de la definición de tipos de objetos de Open Graph Protcol.

La parte que realmente Lo que importa aquí está en nuestro valor de retorno:el nuevo .map() estamos haciendo.

Aquí, hemos introducido una función en la parte superior que devuelve una matriz de objetos con cada objeto que contiene el valor de un name y content atributo en un <meta /> etiqueta. Tenga en cuenta que los nombres cambian según la red social específica, pero la estructura no . Esto es intencional.

Mientras que Twitter y Facebook (el og significa "Open Graph" aquí, que es un estándar creado por Facebook) tienen sus propios nombres de metadatos únicos, ambos usan el mismo mecanismo para compartir esos datos. En nuestro código, podemos aprovechar esto y recorrer una serie de objetos, cada uno de los cuales arroja un <meta /> etiqueta, pasando el name y content para el elemento actual que estamos recorriendo como atributos en la etiqueta.

Para realizar ese ciclo, llamamos al socialTags() función primero, pasando el props para nuestro componente y luego llene dinámicamente la matriz de objetos que la función devuelve con esos valores prop. A cambio, obtenemos una serie de objetos que anticipamos en nuestro return valor.

Allí, encadenamos una llamada a .map() en nuestra llamada a socialTags(props) , y para cada elemento de la matriz devuelta, represente un <meta /> etiqueta con los atributos correspondientes para ese objeto.

Es importante tener en cuenta:lo que ves son solo algunos de las metaetiquetas disponibles para Twitter y Facebook. Dependiendo de su propio sitio, es posible que desee incluir menos o más etiquetas.

Para Twitter, puede hacer referencia a la documentación de marcado de su Tarjeta y para Facebook, hacer referencia a la documentación del protocolo Open Graph.

Con esto en su lugar, ahora, cuando nuestro contenido se comparte en Twitter o Facebook, obtendremos un elemento de "tarjeta" que se muestra correctamente y se ve bien en las líneas de tiempo de las personas.

Adición de metadatos JSON-LD de Google

Antes de poner nuestro <SEO /> componente a usar, queremos agregar un tipo más de metadatos:JSON-LD de Google (el "LD" significa "Datos de enlace"). Estos son los datos que utiliza Google para funciones como sus tarjetas de información en los resultados de búsqueda.

/componentes/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;

Justo debajo de nuestras etiquetas sociales .map() , ahora, hemos agregado un <script /> etiqueta con un type atributo establecido en application/ld+json (el tipo MIME que busca Google cuando busca datos JSON-LD). Debido a que los datos para JSON-LD generalmente se pasan como un objeto entre las etiquetas del script, necesitamos "Reactificarlo" para que no tengamos ningún error de tiempo de ejecución.

Para hacerlo, aprovechamos el dangerouslySetInnerHTML de React prop, pasándole un objeto con un __html atributo establecido en la versión en cadena de nuestro objeto JSON-LD. Cuando esto se represente, React establecerá dinámicamente el objeto aquí como el HTML interno o contenido de nuestro <script /> etiqueta para nosotros (no hay diferencia para Google y funciona de todos modos).

En el objeto, hacemos uso de la estructura JSON-LD, utilizando las definiciones de tipo schema.org para describir nuestros datos.

¡Eso es todo! Para concluir, echemos un vistazo a cómo poner en uso nuestro componente.

Usando nuestro componente SEO

Para poner en uso nuestro componente, vamos a conectar rápidamente una página de ejemplo en nuestro modelo. Para hacerlo, vamos a crear una "publicación de blog" simulada en un archivo llamado /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;

La parte que más nos importa aquí es la representación de nuestro <SEO /> componente. Tenga en cuenta que hemos importado esto en la parte superior de nuestro archivo y lo estamos representando justo dentro del <StyledPost /> componente aquí (este es un tipo especial de componente React conocido como componente con estilo). Entonces lo tiene, muy rápido, aquí está la fuente de ese componente (preste atención a la ruta):

/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;
    }
  }
`;

Aquí, estamos usando el styled-components biblioteca incluida en el modelo de Next.js que estamos usando para ayudarnos a crear dinámicamente un componente React que devuelve un HTML <div /> elemento con el CSS pasado entre los acentos graves aquí como los estilos for que <div /> . El qué y el por qué no son tan importantes para este tutorial, así que después de agregar este archivo, regresemos a nuestra página de publicaciones.

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

Mira nuestra representación del <SEO /> componente, como insinuamos durante su desarrollo, todo lo que estamos haciendo es pasar accesorios con los datos que queremos asignar a nuestras diversas metaetiquetas dentro del componente. Mientras codificamos nuestros accesorios de ejemplo aquí, técnicamente, podría (y probablemente lo hará) usar una expresión React para pasar algún valor variable dependiendo de dónde represente el componente.

Antes de que terminemos esto, muy rápido, queremos llamar la atención sobre el uso de getServerSideProps cerca de la parte inferior de nuestro archivo. Esta es una función que utiliza Next.js para, como su nombre lo indica, obtener accesorios para nuestro componente en un contexto de servidor antes del lado del servidor representa nuestro componente. Esto es importante. La representación del lado del servidor es el término que se usa para describir la respuesta inicial enviada a una solicitud HTTP. Esa respuesta "representa" algo de HTML que recibe el solicitante.

Así funcionan los motores de búsqueda. Los sitios como Google tienen un "rastreador" que visita todas las URL públicas en Internet. Busca esta respuesta inicial para obtener el HTML que utiliza para generar resultados de búsqueda. Esto es precisamente cuando esperamos nuestro <SEO /> componente para ser procesado y "recogido" por los motores de búsqueda.

Aquí, dentro de getServerSideProps queremos obtener la URL base (dominio actual) para la aplicación y pasarla a nuestro componente como accesorio url . Queremos hacer esto para que cuando rendericemos nuestro <SEO /> componente como parte de la respuesta HTML inicial, la URL que pasamos para el url prop en nuestro componente es correcto. Si no haga esto, la respuesta inicial que enviamos a un motor de búsqueda tendría una URL "indefinida".

Con eso, estamos listos para una prueba. Abramos el http://localhost:5000/post página en nuestro navegador web y ver la fuente de nuestra página, comprobando para asegurarse de que nuestros metadatos se muestran como se esperaba:

Excelente. Debido a que vemos nuestros metadatos presentados aquí, podemos confiar en que esto es lo que verá Google (o cualquier otro motor de búsqueda) cuando su rastreador solicite nuestro sitio web.

Terminando

En este tutorial, aprendimos a conectar un <SEO /> personalizado Componente React para ayudarnos a representar dinámicamente las etiquetas de metadatos en función de los accesorios que le pasamos a ese componente. Aprendimos a representar el código HTML básico <meta /> etiquetas, así como las etiquetas necesarias para los sitios de redes sociales como Twitter y Facebook. Finalmente, aprendimos cómo agregar el JSON-LD <script /> de Google a nuestro componente para agregar más contexto y mejorar nuestras posibilidades de clasificación en los resultados de búsqueda.