Gatsby 客户端路由与用户认证:从[...]路由到 PrivateRoute 的完整实战指南
【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby
导读
在 Gatsby 这类以静态站点生成为核心的框架中,登录后的用户中心、控制面板、详情页等动态区域通常无需服务端渲染——数据在用户登录后从 API 实时拉取即可。本文基于官方文档 client-only-routes-and-user-authentication.md 展开,结合仓库内 client-only-paths 与 simple-auth 两个完整示例,系统讲解客户端路由(client-only routes)的搭建方式、与用户认证的结合方案(PrivateRoute),以及部署到托管服务时如何正确配置以支持直接访问这些路由。读完本文,你将能独立实现一个"公开营销页 + 登录 + 私有应用区"的混合型 Gatsby 站点。
理解客户端路由:为什么需要它
一个典型的场景是:站点由落地页、若干营销页、登录页,以及仅供登录用户使用的应用区组成。应用区的数据全部在用户登录后从 API 实时加载,因此不需要也无法被服务端渲染成静态 HTML,把这块做成客户端路由是最合理的选择。
客户端路由的关键特征是:它们只存在于客户端,在构建产物/public目录中不会生成对应的index.html文件。这意味着:
- 用户在站内导航时,客户端路由由 JavaScript 接管渲染,体验流畅;
- 但用户若在地址栏直接输入
/app/xxx这样的地址访问,服务器找不到对应 HTML 文件,就需要托管层配合(详见下文"托管配置"一节)。
Gatsby 会把src/pages下的组件编译为静态 HTML。以文档给出的示意图 client-only-routes.png 为例:Home 页与 App 页生成静态 HTML,App 页内部通过<Router />挂载 Profile、Details 等组件——这些子路由组件不生成静态资源,仅存在于客户端:Profile 页负责向 APIPOST用户数据,Details 页则根据 URL 中的 id 动态加载数据。
用 Gatsby 实现客户端路由
Gatsby 底层使用@reach/router作为路由方案,因此可以直接在由 Gatsby 构建的页面上使用它来声明客户端路由。
第一步:创建[...]客户端路由文件
Gatsby 支持在src/pages中使用[...]文件名创建"仅客户端"的页面,其下的所有路径都由一个组件统一接管。文档中的示例src/pages/app/[...].js:
import React from "react" import { Router } from "@reach/router" // highlight-line import Layout from "../components/Layout" import Profile from "../components/Profile" import Details from "../components/Details" import Login from "../components/Login" import Default from "../components/Default" const App = () => { return ( <Layout> // highlight-start <Router basepath="/app"> <Profile path="/profile" /> <Details path="/details" /> <Login path="/login" /> <Default path="/" /> </Router> // highlight-end </Layout> ) } export default App工作原理简述:页面加载时,Reach Router 会比较每个嵌套在<Router />下的组件的pathprop 与window.location,选择最匹配的一个渲染。例如访问/app/profile时,/app命中 Router 的basepath,剩余部分/profile与子组件路径完全一致,于是渲染Profile组件。
仓库中的 examples/client-only-paths/src/pages/[...].js 给出了一个更复杂的生产级变体:它通过<Location>订阅当前 location,并结合react-transition-group实现路由切换时的淡入淡出过渡动画,其核心仍是<Router location={location}>:
const App = () => ( <div className="app"> <nav className="nav"> <Link to="/">Page 1</Link> <Link to="page/2">Page 2</Link> {` `} <Link to="page/3">Page 3</Link> <Link to="page/4">Page 4</Link> </nav> <FadeTransitionRouter> <Page path="/" page="1" /> <Page path="page/:page" /> </FadeTransitionRouter> </div> )注意page/:page这种带参数的路由写法:访问/page/2时,:page会捕获2并通过 props 传给组件。这正是客户端路由处理"根据 URL 参数加载不同内容"的标准姿势。
matchPath:让客户端路由在开发与构建中"活"起来
仅仅创建[...]文件还不够——Gatsby 需要知道/app/*之下的所有路径都应由这个页面接管。在 simple-auth 示例的 examples/simple-auth/gatsby-node.js 中可以看到标准做法:
exports.onCreatePage = async ({ page, actions }) => { const { createPage } = actions // page.matchPath is a special key that's used for matching pages // only on the client. if (page.path.match(/^\/app/)) { page.matchPath = `/app/*` // Update the page. createPage(page) } }matchPath是 Gatsby 的客户端路由专用字段,通过onCreatePageAPI 写入并重新createPage。在 Gatsby 源码 packages/gatsby/src/bootstrap/requires-writer.ts 中可以看到,所有带matchPath的页面会被单独收集进matchPathPages列表,并用rankRoute(matchPath)按路径优先级排序;其配套测试 requires-writer.js 的 snapshot 也验证了/app/login/、/app/clients/*、/app/*等规则的正确排序与"静态页优先于 matchPath"的行为——这保证了像/app/login这样的精确路由能被正确命中。
从源码结构看,开发模式下matchPath用于让开发服务器的客户端路由回退逻辑生效;构建后,则配合托管平台的 rewrite 规则让直接访问得以实现(见下文)。
结合用户认证:PrivateRoute 组件
文档第二步展示了如何将上述路由扩展为受认证保护的版本。首先在[...]页面中用PrivateRoute包住需要登录才能访问的路由:
import React from "react" import { Router } from "@reach/router" import Layout from "../components/Layout" import Profile from "../components/Profile" import Details from "../components/Details" import Login from "../components/Login" import Default from "../components/Default" import PrivateRoute from "../components/PrivateRoute" // highlight-line const App = () => { return ( <Layout> <Router basepath="/app"> // highlight-start <PrivateRoute path="/profile" component={Profile} /> <PrivateRoute path="/details" component={Details} /> // highlight-end <Login path="/login" /> <Default path="/" /> </Router> </Layout> ) } export default AppPrivateRoute是一个高阶组件包装,其实现(源自 Authentication Tutorial 的"控制私有路由"一节)如下:
import React from "react" import { navigate } from "gatsby" import { isLoggedIn } from "../services/auth" const PrivateRoute = ({ component: Component, location, ...rest }) => { if (!isLoggedIn() && location.pathname !== `/app/login`) { navigate("/app/login") return null } return <Component {...rest} /> } export default PrivateRoute逻辑非常清晰:未登录且不在登录页时,用navigate重定向到/app/login并渲染空节点;已登录则正常渲染目标组件。
仓库中的完整实现:simple-auth 示例
仓库中的 examples/simple-auth 将这一模式完整落地。其页面 examples/simple-auth/src/pages/app.js 同时使用[...](client-only)与matchPath两套机制:
import React from "react" import { Router } from "@reach/router" import Layout from "../components/Layout" import Profile from "../components/Profile" import Details from "../components/Details" import Login from "../components/Login" import PrivateRoute from "../components/PrivateRoute" import Status from "../components/Status" const App = () => ( <Layout> <Status /> <Router> <PrivateRoute path="/app/details" component={Details} /> <PrivateRoute path="/app/profile" component={Profile} /> <Login path="/app/login" /> </Router> </Layout> ) export default App而 examples/simple-auth/src/components/PrivateRoute.js 与文档版本几乎一致,仅增加了PropTypes校验。配套的认证工具函数 examples/simple-auth/src/utils/auth.js 展示了基于localStorage的最小可用认证实现:
handleLogin:校验用户名密码(示例为gatsby/demo),通过后把用户对象写入localStorage.gatsbyUser;isLoggedIn:读取用户对象并判断是否存在email字段;getCurrentUser/logout:获取当前用户、清空登录态。
这里尤其值得注意的是isBrowser守卫(typeof window !== "undefined")——Gatsby 构建期间会执行 Node 环境,访问window会直接报错,所有涉及浏览器 API 的代码都必须做此保护,这是 Gatsby 应用(尤其是客户端路由)的必备常识。
复杂路由下的滚动行为
对于路由结构复杂的应用,客户端路由切换时 Gatsby 默认的滚动恢复行为可能不符合预期。文档建议使用shouldUpdateScrollBrowser API 覆盖默认行为,其完整说明见 gatsby-browser.md 中的shouldUpdateScroll一节,可在gatsby-browser.js中实现该回调来自定义切换路由时的滚动位置。
如何配置托管服务以支持客户端路由
大多数 Gatsby 页面都有对应的 HTML 文件:访问/blog/my-blog-post/时服务器返回/blog/my-blog-post/index.html。但客户端路由如/app/why-gatsby-is-awesome/没有对应 HTML,服务器必须被配置为:把这类请求改写到其客户端路由页面(如/app/[...]/index.html)来处理。
文档说明:Gatsby Cloud、Netlify、Vercel 等主流托管服务都有自动处理客户端路由的方案:
- Gatsby Cloud:使用
gatsby-plugin-gatsby-cloud插件; - Netlify:使用
gatsby-plugin-netlify插件; - Vercel:自动添加其 Gatsby 插件。
仓库中 client-only-paths 示例的 examples/client-only-paths/gatsby-config.js 正是这种实践的样例——它专门加入了gatsby-plugin-netlify,注释写明"used to generate rewrites for client only paths on demo hosted on Netlify":
plugins: [ { resolve: `gatsby-plugin-typography`, options: { pathToConfigModule: `src/utils/typography`, }, }, // used to generate rewrites for client only paths // on demo hosted on Netlify `gatsby-plugin-netlify`, ],部署到 Netlify 的整体流程可参考 deploying-to-netlify.md。
自托管:NGINX 与 Apache
如果你的站点自托管,需要手动配置服务器:对/app/*的GET请求(如/app/why-gatsby-is-awesome)返回/app/[...]/index.html,由客户端接管渲染。关键点:响应码必须是 200(OK),而不是 301(重定向)——301 会改变地址栏 URL 并导致客户端路由参数丢失。
- NGINX:使用
try_files指令尝试多个候选文件,命中失败时回退到客户端路由页面; - Apache:使用与
try_files等价的指令(如mod_rewrite的RewriteRule)实现同样的回退逻辑。
调试与验证清单
完成上述配置后,建议按以下顺序验证:
- 构建检查:运行
gatsby build,确认public目录中不存在/app/profile/index.html之类的文件——这是客户端路由的预期行为; - 站内导航:从首页点击进入
/app/profile,确认无需刷新即可渲染(客户端路由生效); - 直接访问:在地址栏直接输入
/app/profile,确认托管层 rewrite 生效、返回 200 且页面正确渲染(而非 404 或 301 跳转); - 认证拦截:退出登录后访问
/app/profile,应被PrivateRoute重定向到/app/login; - 带参路由:访问
/app/details/42这类 URL,确认:page形式的参数能被正确解析并传入组件。
延伸阅读
- Building a site with authentication:完整的认证接入指南,是
PrivateRoute方案的前置依赖; - Authentication Tutorial:手把手教程,含
PrivateRoute的完整演进过程; - Creating Routes:Gatsby 路由机制的总体介绍;
- gatsby-browser.md:
shouldUpdateScroll等浏览器端 API 参考; - 仓库示例:examples/client-only-paths 与 examples/simple-auth 可直接运行验证(
npm install后执行npm run develop)。
【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考