在我构建网站时,这个看似简单的任务让我苦恼了几个小时。 事实证明,在 Gatsby 中获取当前页面 URL 并不像你想象的那么简单,但也并不难理解。
让我们看看实现它的几种方法。 但首先,您可能想知道我们为什么要做这样的事情。
为什么您可能需要当前 URL
所以在我们进入“如何”之前,让我们先回答一个更大的问题:为什么要获取当前页面的 URL? 我可以提供一些用例。
元标签
您想要获取当前 URL 的第一个显而易见的事情是文档头中的元标签
<link rel="canonical" href={url} />
<meta property="og:url" content={url} />
社交分享
我在多个网站上看到,在分享按钮旁边显示指向当前页面的链接。 类似于此(在 Creative Market 上找到)

样式
这个不太明显,但我用过几次 styled-components。 您可以根据某些条件呈现不同的样式。 其中一个条件可以是页面路径(即站点名称后 URL 的一部分)。 这是一个快速示例
import React from 'react';
import styled from 'styled-components';
const Layout = ({ path, children }) => (
<StyledLayout path={path}>
{children}
</StyledLayout>
);
const StyledLayout = styled.main`
background-color: ${({ path }) => (path === '/' ? '#fff' : '#000')};
`;
export default Layout;
在这里,我创建了一个样式化的 Layout
组件,它根据路径具有不同的背景颜色。
此示例列表仅说明了这个想法,绝不是详尽无遗的。 我相信还有更多情况下您可能希望获取当前页面 URL。 那么我们如何获取它呢?
了解构建时间与运行时间
别急! 在我们进入实际的方法和代码片段之前,我想再停一下,简要解释一下 Gatsby 的一些核心概念。
我们需要理解的第一件事是,Gatsby 除了其他许多功能外,还是一个静态网站生成器。 这意味着它创建静态文件(通常是 HTML 和 JavaScript)。 生产网站上没有服务器和数据库。 所有信息(包括当前页面 URL)都必须从其他来源获取或在**构建时间**或**运行时间**生成,然后将其插入标记中。
这将我们引向我们需要理解的第二个重要概念:构建时间与运行时间。 我建议您阅读官方的 Gatsby 文档,但这是我的理解。
**运行时**是在**浏览器**中打开静态页面之一时。 在这种情况下,页面可以访问所有很棒的浏览器 API,包括 Window API,它除了其他许多功能外,还包含当前页面 URL。
尤其是在刚开始使用 Gatsby 时,很容易混淆的一件事是,在开发模式下在终端中运行 gatsby develop
会为您启动浏览器。 这意味着对窗口对象的引用都可以工作并且不会触发任何错误。
**构建时间**是在您完成开发并告诉 Gatsby 使用 gatsby build
命令生成最终优化资产时发生的。 在构建时间,浏览器不存在。 这意味着您无法使用窗口对象。
现在是“啊哈!”时刻。 如果构建与浏览器隔离,并且没有服务器或数据库可以获取 URL,那么 Gatsby 如何知道正在使用哪个域名? 问题就在这里——它不知道! 您可以获取页面的 slug 或路径,但您根本无法确定基本 URL 是什么。 您必须指定它。
这是一个非常基本的概念,但如果您是带着多年的 WordPress 经验来使用 Gatsby 的,那么可能需要一段时间才能理解这一点。 您知道 Gatsby 是无服务器的,但像这样的时刻让您意识到:**没有服务器。**
现在我们已经解决了这个问题,让我们跳到获取当前页面 URL 的实际方法。
方法 1:使用 window.location 对象的 href 属性
此第一种方法不是特定于 Gatsby 的,可以在浏览器中几乎任何 JavaScript 应用程序中使用。 看,浏览器是这里的关键词。
假设您正在构建那些带有输入字段的共享组件之一,该输入字段必须包含当前页面的 URL。 以下是如何操作
import React from 'react';
const Foo = () => {
const url = typeof window !== 'undefined' ? window.location.href : '';
return (
<input type="text" readOnly="readonly" value={url} />
);
};
export default Foo;
如果 window
对象存在,我们将获取 location
对象的 href
属性,它是 window
的子对象。 如果不存在,我们将为 url
变量赋予空字符串值。
如果我们不进行检查并像这样编写它
const url = window.location.href;
…构建将失败并出现类似于此的错误
failed Building static HTML for pages - 2.431s
ERROR #95312
"window" is not available during server-side rendering.
正如我之前提到的,发生这种情况是因为浏览器在构建时间不存在。 这是此方法的一个巨大缺点。 如果您需要 URL 出现在页面的静态版本上,则无法使用它。
但它也有一个很大的优势! 您可以从嵌套在其他组件深处的组件中访问窗口对象。 换句话说,您不必从父组件中提取 URL 属性。
方法 2:从 props 中获取 location 数据的 href 属性
Gatsby 中的每个页面和模板组件都有一个 location 属性,其中包含有关当前页面的信息。 然而,与 window.location
不同,此属性存在于所有页面上。
引用 Gatsby 文档
最棒的是,您可以在每个页面上都期望 location 属性可用。
但这里可能有一个陷阱。 如果您是 Gatsby 的新手,您会将该属性记录到控制台,并注意到它看起来与 window.location
非常相似(但不是同一件事),并且还包含 href
属性。 这是怎么发生的? 好吧,它不是。 href
属性仅在运行时存在。
最糟糕的是,在没有先检查它是否存在的情况下直接使用 location.href
不会在构建时触发错误。
所有这些意味着我们可以依赖每个页面上的 location
属性,但在构建时不能期望它具有 href
属性。 请注意这一点,并且不要在需要 URL 出现在页面静态版本标记中的关键情况下使用此方法。
因此,让我们使用此方法重写前面的示例
import React from 'react';
const Page = ({ location }) => {
const url = location.href ? location.href : '';
return (
<input type="text" readOnly="readonly" value={url} />
);
};
export default Page;
这必须是顶级页面或模板组件。 您不能在任何地方导入它并期望它能工作。 location
属性将未定义。
如您所见,此方法与前一种方法非常相似。 在仅在运行时需要 URL 的情况下使用它。
但是,如果您需要在静态页面的标记中包含完整的 URL 该怎么办? 让我们继续第三种方法。
方法 3:使用 location 数据中的 pathname 属性生成当前页面 URL
正如我们在本文开头所讨论的,如果您需要将完整 URL 包含到静态页面中,则必须在某个地方为网站指定基本 URL,并在构建时以某种方式获取它。我将向您展示如何做到这一点。
例如,我将在头部创建一个 <link rel="canonical" href={url} />
标签。在页面加载到浏览器之前,在其中包含完整的页面 URL 非常重要。否则,搜索引擎和网站抓取工具将看到空的 href
属性,这是不可接受的。
以下是计划
- 将
siteURL
属性添加到gatsby-config.js
中的siteMetadata
。 - 创建一个静态查询钩子,以便在任何组件中检索
siteMetadata
。 - 使用该钩子获取
siteURL
。 - 将其与页面的路径组合,并将其添加到标记中。
让我们逐一分解每个步骤。
将 siteURL 属性添加到 gatsby-config.js 中的 siteMetadata
Gatsby 具有一个名为 gatsby-config.js
的配置文件,可用于将有关站点的全局信息存储在 siteMetadata
对象中。这对我们有用,因此我们将 siteURL
添加到该对象中。
module.exports = {
siteMetadata: {
title: 'Dmitry Mayorov',
description: 'Dmitry is a front-end developer who builds cool sites.',
author: '@dmtrmrv',
siteURL: 'https://dmtrmrv.com',
}
};
创建静态查询钩子以在任何组件中检索 siteMetadata
接下来,我们需要一种方法在我们的组件中使用 siteMetadata
。幸运的是,Gatsby 具有一个 StaticQuery API,它允许我们做到这一点。您可以在组件内部直接使用 useStaticQuery
钩子,但我更喜欢为我在网站上使用的每个静态查询创建一个单独的文件。这使得代码更易于阅读。
为此,请在您网站的 src
文件夹内的 hooks
文件夹中创建一个名为 use-site-metadata.js
的文件,并将以下代码复制粘贴到其中。
import { useStaticQuery, graphql } from 'gatsby';
const useSiteMetadata = () => {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
author
siteURL
}
}
}
`,
);
return site.siteMetadata;
};
export default useSiteMetadata;
请确保检查所有属性(如 title
、description
、author
以及 siteMetadata
对象中存在的任何其他属性)是否都出现在 GraphQL 查询中。
使用该钩子获取 siteURL
这是有趣的部分:我们获取站点 URL 并将其在组件内部使用。
import React from 'react';
import Helmet from 'react-helmet';
import useSiteMetadata from '../hooks/use-site-metadata';
const Page = ({ location }) => {
const { siteURL } = useSiteMetadata();
return (
<Helmet>
<link rel="canonical" href={`${siteURL}${location.pathname}`} />
</Helmet>
);
};
export default Page;
让我们逐一分解。
在第 3 行,我们将创建的 useSiteMetadata
钩子导入到组件中。
import useSiteMetadata from '../hooks/use-site-metadata';
然后,在第 6 行,我们解构来自它的数据,创建 siteURL
变量。现在我们有了在构建和运行时都可用的站点 URL。太棒了!
const { siteURL } = useSiteMetadata();
将站点 URL 与页面的路径组合,并将其添加到标记中
现在,还记得第二种方法中的 location 属性吗?它很棒的一点是,它在构建和运行时都包含 pathname
属性。看到它要往哪里发展了吗?我们只需将两者结合起来即可。
`${siteURL}${location.pathname}`
这可能是最可靠的解决方案,可以在浏览器和生产构建中使用。我个人最常使用此方法。
在此示例中,我使用的是 React Helmet。如果您从未听说过它,它是一个用于在 React 应用程序中呈现头部部分的工具。Darrell Hoffman 在这里对它进行了很好的解释,在 CSS-Tricks 上。
方法 4:在服务器端生成当前页面 URL
什么?!你刚才说服务器?Gatsby 不是静态网站生成器吗?是的,我确实说了服务器。但它不是传统意义上的服务器。
如我们所知,Gatsby 在构建时生成(即服务器渲染)静态页面。这就是名称的由来。这样做的好处是,我们可以使用 Gatsby 已提供的 多个 API 来挂钩到该过程中。
我们最感兴趣的 API 称为 onRenderBody
。大多数情况下,它用于将自定义脚本和样式注入到页面中。但令人兴奋的是(以及其他服务器端 API),它具有 pathname
参数。这意味着我们可以在“服务器”上生成当前页面 URL。
我个人不会使用此方法将元标记添加到头部部分,因为我们看到的第三种方法更适合这样做。但为了举例说明,让我向您展示如何使用 onRenderBody
将规范链接添加到站点。
要使用任何服务器端 API,您需要在名为 gatsby-ssr.js
的文件中编写代码,该文件位于您网站的根文件夹中。要将链接添加到头部部分,您可以编写如下代码
const React = require('react');
const config = require('./gatsby-config');
exports.onRenderBody = ({ pathname, setHeadComponents }) => {
setHeadComponents([
<link rel="canonical" href={`${config.siteMetadata.siteURL}${pathname}`} />,
]);
};
让我们一点一点地分解这段代码。
我们在第 1 行需要 React。这对于使 JSX 语法起作用是必要的。然后,在第 2 行,我们将数据从 gatsby-config.js
文件中提取到 config
变量中。
接下来,我们在 onRenderBody
内部调用 setHeadComponents
方法,并将要添加到站点头部的组件数组传递给它。在我们的例子中,它只是一个链接标签。对于链接本身的 href 属性,我们将 siteURL
和 pathname
组合在一起。
`${config.siteMetadata.siteURL}${pathname}`
就像我之前说的,这可能不是将标签添加到头部部分的首选方法,但了解 Gatsby 具有服务器端 API,这使得能够在服务器渲染阶段为任何给定页面生成 URL,这一点很重要。
如果您想了解有关使用 Gatsby 进行服务器端渲染的更多信息,我建议您阅读他们的 官方文档。
就是这样!
如您所见,在 Gatsby 中获取当前页面的 URL 并不复杂,尤其是在您理解核心概念并了解可用的工具之后。如果您知道其他方法,请在评论中告诉我!
天哪,为了访问页面 URL 而使用了这么多的 JS。在大多数其他 SSG 中,这实际上只是 {{ page.url }}。
嘿,Max!是的,我明白你的意思,对于这样一个简单的任务,代码看起来很多。但实际上,您大多数时候都会使用
`${siteURL}${location.pathname}`
的变体。这只是理解这些部分来自哪里。您好,非常感谢。我一直为此而苦恼,最终得到了与您的第 3 种方法相同的解决方案。特别是,为了使用 gatsby-plugin-sitemap,我需要设置 siteMetadata.siteUrl。
谢谢,Paulina!是的,那通常是我的首选方法。我自己也用
siteMetadata
做了很多事情!谢谢!
感谢你的帖子,
一篇很棒的帖子……
Gatsby 在底层使用了 React Router,所以你也可以使用 Location renderProp。
你好,我尝试了第三种方法,但它对我不起作用。使用这行代码:const url =
${site.siteMetadata.siteURL}${location.pathname}
它在本地环境工作,但在 Netlify 上报错:“location” 在服务器端渲染期间不可用。所以,它似乎并不总是有效。我们不能使用
${location.origin}${location.pathname}
吗?大家好,我找到了简写方法,它可以将布尔值传递给我的导航栏。
爱你们所有人
<Navbar
isIndex={props=>(props.location.pathname === "/" ? true : false)}
/>
如果你期望方法4在本地环境工作,那是不可能的。请查看这里:https://github.com/gatsbyjs/gatsby/issues/4350#issuecomment-370182332
如果你部署到 Netlify 或其他平台,如果你直接访问页面或导航并刷新,它应该可以正常工作。
如果你使用 Netlify,这可能会有所帮助:我可以将 domain() 用于我的
<helmet>
社交分享标签和其他分享场景。该方法返回 Netlify 提供的测试域名(分支部署)或配置的主域名。作为水合案例(意味着我们有浏览器)的回退,它使用浏览器主机名。https://github.com/FixMyBerlin/fixmy.safetycheck/blob/develop/src/components/utils/domain.ts