JavaScript >> Javascript 文檔 >  >> Tags >> Next.js

如何在 Next.js 中處理 SEO 元數據

如何創建自定義 React 組件以呈現 SEO 元數據標籤以及包含哪些標籤以提高整個網絡的排名和性能。

開始使用

對於本教程,我們將使用 CheatCode Next.js 樣板作為我們工作的起點。首先,讓我們從 Github 克隆一個副本:

終端

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

接下來,cd 進入項目並安裝其依賴項:

終端

cd nextjs-boilerplate && npm install

最後,繼續啟動開發服務器:

終端

npm run dev

有了這個,我們就可以開始了。

創建 SEO 組件

對於本教程,我們將創建一個 React 組件,該組件將幫助我們在 Next.js 中呈現我們的 SEO 數據。這將使我們可以輕鬆地將 SEO 元數據添加到任何頁面,只需更改我們傳遞給組件的 props。

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

關注文件頂部附近,對於我們的 React 組件,我們將使用函數組件模式,因為我們不需要訪問該組件的任何生命週期方法或狀態。

在文件的頂部,我們從 next/head 導入另一個 React 組件 包,<Head /> .這是 Next.js 內置的組件,可幫助我們定義將在 <head></head> 中顯示的數據(由 Next.js 自動顯示) 呈現此組件的頁面的 HTML 標記。

對於我們的的返回值 組件—<SEO /> ——我們渲染這個 <Head /> 組件作為打開和關閉標籤。這意味著 React 打開和關閉標籤之間的內容是該組件的子組件。雖然我們看不到,但 React 有一個標準的 prop children 就像我們在上面看到的那樣,它被分配了在打開和關閉標籤之間傳遞的標記。在內部,<Head /> 組件讀取此 children prop 並使用它來填充 <head></head> 頁面呈現的 HTML 中的標記(返回給 Google 和其他搜索引擎的內容)。

在這些標籤之間,我們傳遞一個標準的 HTML <title /> 標籤以及一系列 <meta /> 標籤。即使我們在一個 React 組件中,這個標記也代表純 HTML。如果我們要復制這些標籤並將它們粘貼到 <head></head> 一個普通的 .html 文件,瀏覽器會毫無問題地呈現它們。

在這裡,因為我們在 React 內部——或者更準確地說,JSX,React 使用的標記語言——我們可以使用花括號將動態值(這裡稱為 React 表達式)傳遞給這些標籤的屬性。在上面的代碼中,就在我們組件的函數體內部,我們使用 JavaScript 對象解構來“提取”title , description , 和 image 我們預計會被傳遞給我們的 <SEO /> 組件。

假設這些已定義,我們使用 React 表達式在元數據標記中設置這些,呈現 title<title></title> 標記和其他標記為 content 各自<meta />的屬性 標籤。這裡需要注意的是:因為我們試圖覆蓋我們所有的基礎以實現 SEO,所以我們將看到數據被多次傳遞給不同的標籤。這是故意的。這是因為不同的搜索引擎會以不同的方式解析我們的數據。通過這樣做,我們確保了我們內容的最大兼容性。

在我們文件的底部,我們會注意到我們正在利用 React 的 .defaultProps.propTypes 我們組件的屬性。後者,.propTypes ,旨在幫助我們驗證傳遞給我們的道具的內容。這適用於我們作為開發人員,不會影響我們的用戶。在這裡,使用 PropTypes 我們在頂部導入的對象,我們設置了三個 props title 的期望值 , description , 和 image 都包含一個字符串值(在我們的代碼中指定為 PropTypes.string )。

在此之上,我們還定義了一些 defaultProps 對於我們的 <SEO /> 零件。這個很重要。請注意,在這裡,我們正在訪問一個值 settings 我們假設它是從我們項目的其他地方導入的對象。在我們在教程開始時克隆的樣板文件中,存在一個約定,用於根據當前環境或 process.env.NODE_ENV 的值加載任意設置的文件 .默認情況下,此值為 development 所以我們希望樣板文件已經加載了 /settings/settings-development.js 的內容 給我們存檔。

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

值得注意的是,我們會看到在這些設置中,meta 對象設置為一系列鍵/值對。該數據被設置為我們整個網站的默認 SEO 元數據(或者,換句話說,如果我們不為 <SEO /> 的 props 傳遞任何值,我們將依賴的後備數據 組件)。

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

回到我們的組件中,我們可以看到我們正在拉入該設置文件,並且在我們的 .defaultProps 對象,傳遞 meta 的內容 該文件中的對象。同樣,這確保瞭如果我們這樣做 not 通過這些道具,我們會有一些 傳遞的數據與空字符串或“未定義”值相反。

為社交媒體添加元數據標籤

雖然我們在上面看到的代碼肯定會讓我們開始滿足我們的 SEO 需求,但在現代網絡術語中,這就像拿刀來進行槍戰一樣。因為網絡已經擴散到不同的社交網絡和越來越複雜的搜索引擎算法,它有助於我們的案例為了排名添加更多的具體數據。

特別是,我們希望添加對來自兩個大型網站的社交數據的支持:Twitter 和 Facebook。幸運的是,雖然我們需要支持更多標籤,但這些標籤的結構非常相似,我們可以將它們的大部分輸出自動化。

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

在我們深入研究社交標籤之前,很快,我們想提請注意 urlopenGraphType 我們添加到 propTypes 的字段 和 defaultProps .這些代表 url 我們當前所在的頁面(例如,如果我們在類似 /posts/the-slug-of-the-post 的博客文章中 ) 和一個 openGraphType 它將映射到 Open Graph Protcol 的對像類型定義中的類型值。

我們真正的部分 這里關心的是我們的返回值:新的.map() 我們正在做。

在這裡,我們在頂部引入了一個函數,它返回一個對像數組,每個對像都包含 name 的值 和 content <meta /> 上的屬性 標籤。請注意,名稱會根據特定的社交網絡而變化,但結構不會 .這是故意的。

而 Twitter 和 Facebook(og 在這裡代表“Open Graph”,這是 Facebook 創建的標準)具有自己獨特的元數據名稱,它們都使用相同的機制來共享該數據。在我們的代碼中,我們可以利用這一點並循環遍歷一組對象,每個對像都輸出一個 <meta /> 標籤,傳遞 namecontent 對於當前項目,我們作為標籤上的屬性進行循環。

為了執行那個循環,我們調用 socialTags() 函數優先,傳入props 對於我們的組件,然後使用這些 prop 值動態填充函數返回的對像數組。作為回報,我們在 return 中返回我們預期的對像數組 價值。

在那裡,我們將調用鏈接到 .map() 關於我們對 socialTags(props) 的調用 ,並且對於返回數組中的每個項目,呈現一個 <meta /> 帶有該對象相應屬性的標記。

需要注意的是:你看到的只是一些 Twitter 和 Facebook 的可用元標記。根據您自己的網站,您可能希望包含更少或更多的標籤。

對於 Twitter,您可以參考他們的 Card 標記文檔;對於 Facebook,請參考 Open Graph 協議文檔。

有了這些,現在,當我們在 Twitter 或 Facebook 上分享我們的內容時,我們將得到一個正確顯示的“卡片”元素,在人們的時間線中看起來不錯。

添加 Google JSON-LD 元數據

在我們放 <SEO /> 之前 要使用的組件,我們要添加另一種類型的元數據:Google 的 JSON-LD(“LD”代表“鏈接數據”)。這是 Google 用於搜索結果中的信息卡等功能的數據。

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

在我們的社交標籤 .map() 下方 ,現在,我們添加了一個 <script /> 帶有 type 的標籤 屬性設置為 application/ld+json (Google 在檢查 JSON-LD 數據時查找的 MIME 類型)。因為 JSON-LD 的數據通常作為對像傳遞 between 腳本標籤,我們需要“React-ify”它,這樣我們就不會得到任何運行時錯誤。

為此,我們利用了 React 的 dangerouslySetInnerHTML prop,傳遞一個帶有 __html 的對象 屬性設置為我們的 JSON-LD 對象的字符串化版本。當這個渲染時,React 將這裡的對象動態設置為 inner HTML 或我們的 <script /> 的內容 為我們標記(對 Google 沒有任何影響,並且工作方式相同)。

在對像上,我們使用 JSON-LD 結構,使用 schema.org 類型定義來描述我們的數據。

而已!最後,讓我們看看如何使用我們的組件。

使用我們的 SEO 組件

為了使用我們的組件,我們將在樣板文件中快速連接一個示例頁面。為此,我們將在名為 /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;

這裡我們最關心的部分是我們的<SEO />的渲染 零件。請注意,我們已經在文件頂部導入了它,並在 <StyledPost /> 內呈現它 這裡的組件(這是一種特殊類型的 React 組件,稱為樣式化組件)。所以你有它,真的很快,這是該組件的來源(注意路徑):

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

在這裡,我們使用 styled-components Next.js 樣板中包含的庫,我們使用它來幫助我們動態創建一個返回 HTML <div /> 的 React 組件 帶有 CSS 的元素在此處作為樣式 for 在反引號之間傳遞 那 <div /> .內容和原因對於本教程來說並不是很重要,所以在你添加了這個文件之後,讓我們跳回我們的帖子頁面。

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

看看我們對 <SEO /> 的渲染 組件,就像我們在其開發過程中所暗示的那樣,我們所做的只是將帶有我們想要映射到組件內的各種元標記的數據的道具傳遞給我們。雖然我們在這裡對示例道具進行硬編碼,但從技術上講,您可以(並且可能會)使用 React 表達式來傳遞一些變量值,具體取決於您渲染組件的位置。

在我們快速完成之前,我們想提請注意 getServerSideProps 的用法 在我們文件的底部附近。這是 Next.js 使用的一個函數,顧名思義,在服務器上下文 before 中獲取我們組件的任何 props 它在服務器端呈現我們的組件。這個很重要。服務器端呈現是用於描述發送回 HTTP 請求的初始響應的術語。該響應“呈現”請求者收到的一些 HTML。

這就是搜索引擎的工作方式。像谷歌這樣的網站有一個“爬蟲”,可以訪問互聯網上的所有公共 URL。它查找此初始響應以獲取用於生成搜索結果的 HTML。這正是我們期望我們的 <SEO /> 被搜索引擎渲染和“拾取”的組件。

這裡,在 getServerSideProps 裡面 我們想要獲取應用程序的基本 URL(當前域)並將其作為 prop url 傳遞給我們的組件 .我們希望這樣做,以便在渲染 <SEO /> 時 組件作為初始 HTML 響應的一部分,我們為 url 傳遞的 URL 我們組件上的 prop 是正確的。如果我們沒有 這樣做,我們發送回搜索引擎的初始響應將有一個“未定義”的 URL。

有了這個,我們就可以進行測試了。讓我們打開 http://localhost:5000/post 在我們的網絡瀏覽器中查看頁面並查看我們頁面的源代碼,檢查以確保我們的元數據按預期呈現:

偉大的。因為我們看到這裡呈現了我們的元數據,所以我們可以相信這是 Google(或任何其他搜索引擎會在其爬蟲請求我們的網站時看到的)。

總結

在本教程中,我們學習瞭如何連接自定義 <SEO /> React 組件可幫助我們根據傳遞給該組件的 props 動態呈現元數據標籤。我們學習瞭如何渲染基本的 HTML <meta /> 標籤,以及 Twitter 和 Facebook 等社交媒體網站所需的標籤。最後,我們學習瞭如何添加 Google 的 JSON-LD <script /> 添加到我們的組件中以添加更多上下文並提高我們在搜索結果中排名的機會。


Tutorial JavaScript 教程
  1. 使用 CSS Houdini 進行漸進式增強和最終想法

  2. 如何將 Tailwindcss 與您的 Vue/Vite 項目集成

  3. 一個初學者嘗試通過做一個項目來理解和使用節點

  4. Firefox 79:共享內存、新工具和平台更新的安全回歸

  5. axios HTTP 請求

  6. javascript:函數中的可選第一個參數

  7. 我對構造函數犯的一個微妙錯誤

  1. 如何在 Windows 上設置 PostgreSQL

  2. Webpack – Typescript – Babel Loader 不轉譯 JSON 導入

  3. Appwrite 是一個開源後端,旨在為您的新項目提供更好的起點

  4. Sapper + Svelte + tailwindcss 樣板

  5. Web 開發的前 5 名 IDE 🤩

  6. 為什麼nodejs在異步函數中停止執行while true循環

  7. 教程:將 Sitemap.xml 和 Robots.txt 添加到 Remix 站點

  1. NodeJS 中的 HLS 音頻流

  2. 如何阻止我的組件在開發中被製造兩次

  3. 使用 Javascript 進行語音識別

  4. 究竟如何創建一個自定義的 jQuery Accordion