You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gatsby结合Contentful实现client-only路由私有内容权限控制问题

Gatsby client-only路由权限控制问题

目标

计划为/dashboard路径下的内容使用client-only路由,该路径下部分内容来自Contentful,将通过页面模板渲染,路由示例为{MYDOMAIN}/dashboard/{SLUG_FROM_CONTENTFUL},目的是确保代理公司参与的项目内容无法被爬虫抓取或公开访问,仅登录后的「雇主」可见。

已尝试方案

页面通过gatsby-node.js生成,认证与client-only路由的实现参考Gatsby官方认证教程,目前基础功能运行正常:登录状态下访问/dashboard会加载Profile.js,未登录状态下访问/dashboard会加载Login.js。
但存在异常:未登录状态下直接访问/dashboard/url-from-contentful时,不会跳转至/dashboard/login,而是直接返回对应页面内容。
相关代码如下:

gatsby-node.js 代码

exports.createPages = async ({graphql, actions}) => {
    const { createPage } = actions;
    
    const { data } = await graphql(`
        query {
            agency: allContentfulAgency {
                edges {
                    node {
                        slug
                    }
                }
            }
        }
    `);
    data.agency.edges.forEach(({ node }) => {
        createPage({
            path: `dashboard/${node.slug}`,
            component: path.resolve("src/templates/agency-template.js"),
            context: {
                slug: node.slug,
            },
        });
    });
}

exports.onCreatePage = async ({ page, actions }) => {
    const { createPage } = actions;
    
    if(page.path.match(/^\/dashboard/)) {
        page.matchPath = "/dashboard/*";
        
        createPage(page);
    }
};

auth.js 代码

export const isBrowser = () => typeof window !== "undefined";

export const getUser = () =>
  isBrowser() && window.localStorage.getItem("gatsbyUser")
    ? JSON.parse(window.localStorage.getItem("gatsbyUser"))
    : {};

const setUser = (user) =>
  window.localStorage.setItem("gatsbyUser", JSON.stringify(user));

export const handleLogin = ({ username, password }) => {
  if (username === `john` && password === `pass`) {
    return setUser({
      username: `john`,
      name: `Johnny`,
      email: `johnny@example.org`,
    });
  }

  return false;
};

export const isLoggedIn = () => {
  const user = getUser();

  return !!user.username;
};

export const logout = (callback) => {
  setUser({});
  call
};

PrivateRoute.js 代码

import React from "react";
import { navigate } from "gatsby";
import { isLoggedIn } from "../services/auth";

const PrivateRoute = ({ component: Component, location, ...rest }) => {
  if (!isLoggedIn() && location.pathname !== `/dashboard/login`) {
    navigate("/dashboard/login");
    return null;
  }

  return <Component {...rest} />;
};

export default PrivateRoute;

dashboard.js 代码

import React from "react";
import { Router } from "@reach/router";
import Layout from "../components/Layout";
import Profile from "../components/Profile";
import Login from "../components/Login";
import PrivateRoute from "../components/PrivateRoute";
import Agency from "../templates/agency-template";

const App = () => (
  <Layout>
    <Router>
      <PrivateRoute path="/dashboard/url-from-contentful" component={Agency} />
      <PrivateRoute path="/dashboard/profile" component={Profile} />
      <PrivateRoute path="/dashboard" />
      <Login path="/dashboard/login" />
    </Router>
  </Layout>
);

export default App;

agency-template.js 代码

import React from "react";
import { graphql, Link } from "gatsby";
import styled from "styled-components";
import SEO from "../components/SEO";
import Layout from "../components/Layout";
import Gallery from "../components/Gallery";
import GeneralContent from "../components/GeneralContent/GeneralContent";

const agencyTemplate = ({ data }) => {
  const {
    name,
    excerpt,
    richDescription,
    richDescription: { raw },
    images,
    technology,
    website,
  } = data.agency;

  const [mainImage, ...projectImages] = images;

  return (
    <>
      <SEO title={name} description={excerpt} />
      <Layout>
        <div className="container__body">
          <GeneralContent title={name} />
          <Gallery mainImage={mainImage} />

          <GeneralContent title="Project Details" content={richDescription} />
          <div className="standard__images">
            <Gallery projectImages={projectImages} />
          </div>
          <ViewWebsite>
            <Link className="btn" to={website}>
              View the website
            </Link>
          </ViewWebsite>
        </div>
      </Layout>
    </>
  );
};

export const query = graphql`
  query ($slug: String!) {
    agency: contentfulAgency(slug: { eq: $slug }) {
      name
      excerpt
      technology
      website
      images {
        description
        gatsbyImageData(
          layout: FULL_WIDTH
          placeholder: TRACED_SVG
          formats: [AUTO, WEBP]
          quality: 90
        )
      }
      richDescription {
        raw
      }
    }
  }
`;
export default agencyTemplate;

Gatsby作为SSG应该可以实现CMS内容的访问限制,提问者对client-only路由的底层逻辑理解存在偏差,且刚接触React和Gatsby,希望获得相关指导。

最终解决方案

调整了实现逻辑:不再通过gatsby-node.js调用模板生成页面,改为采用React原生路由逻辑,查询到Contentful数据后通过map方法动态生成Agency组件路由,同时结合useContext做状态管理、引入Auth0实现认证,解决了权限控制问题。相关代码如下:

return (
    <>
      <Router>
        <DashboardArea path="/dashboard/" user={user} />
        {agencyData.map(({ node }, index) =>
          node.slug ? (
            <Agency key={index} data={node} path={`/dashboard/${node.slug}`} />
          ) : null
        )}
      </Router>
    </>
  );

内容的提问来源于stack exchange,提问作者Bagseye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 09:57:04