将仅客户端路由与来自 Contentful 的页面模板一起使用

Bag*_*eye 5 javascript content-management-system reactjs gatsby

目标

我希望对特定 URL ( /dashboard)下的内容使用仅客户端路由。其中一些内容将来自 Contentful 并使用页面模板。这条路线的一个例子是{MYDOMAIN}/dashboard/{SLUG_FROM_CONTENTFUL}。这样做的目的是确保我在代理机构工作过的项目无法被抓取/访问,并且只有“雇主”登录后才能看到。

我试过的

我的页面是通过gatsby-node.js. 添加身份验证/仅客户端路由的方式取自此示例。现在它的基础已经设置好并且工作正常,据我所知。但私人路线似乎只在以下情况下有效:

如果我已登录并导航到 /dashboard

  • 我显示 Profile.js

如果我没有登录并转到 /dashboard

  • 我显示 Login.js

所以这一切似乎都很好。问题出现在我去/dashboard/url-from-contentful但我没有登录时。我得到的是页面而不是被发送到/dashboard/login.


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);
    }
};
Run Code Online (Sandbox Code Playgroud)

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
};
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

dashboard.js有以下内容。该行<PrivateRoute path="/dashboard/url-from-contentful" component={Agency} />,我在这里尝试了几件事 - 静态输入路线并使用exact道具,使用路线参数,例如/:id, /:path, /:slug


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;

Run Code Online (Sandbox Code Playgroud)

最后 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;

Run Code Online (Sandbox Code Playgroud)

我认为 Gatsby 可以控制来自 CMS 的内容,但鉴于它是 SSG,我可能错了。我可能误解了仅客户端的基本原理。React 中的概念和使用 Gatsby 对我来说仍然很新,因此将不胜感激任何帮助或指导实现目标。

我最终做了什么

所以我标记的答案是“让球滚动”的答案。对 state 正在发生的事情的解释以及需要useContextor 或 redux 帮助我理解我哪里出错了。

此外,使用 Web 令牌的建议促使我找到有关在应用程序中使用 Auth0 的更多信息。

一旦我摆脱了使用 Gatsby 创建页面的心态(通过模板,通过gatsby-node.s),而是通过处理路由和 GraphQL 以“React 方式”(我知道 Gatsby 是用 React 构建的)来做,它变得更加清晰。除了身份验证,我最终要做的就是创建一个新<Agency />组件并将 GraphQL 中的数据提供给它,并使用我的map().

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

Wal*_*riq 1

我假设在您的 PrivateRoute 组件中,您错误地使用了 isLoggedIn 检查。从 auth.js 导入和使用 isLoggedIn 将仅在最初运行,并且不会充当监听器。您可以做的是将 isLoggedin 的值存储在全局状态变量(例如 useContext 或 redux)中,并创建一个自定义挂钩来检查登录状态。其次避免直接访问localStorage,而是使用全局状态管理(useContext,redux)或本地状态管理(useState,this.state)。注意:当您通过直接在浏览器中粘贴网址进入路线时,它总是会刷新页面并且所有存储的状态都会重新初始化。这可能就是您遇到此问题的原因。浏览器不知道您之前是否已登录,因此一旦安装您的应用程序,它总是会进行验证。您可以做的是将 isLoggedIn 状态存储在浏览器的本地存储中。我个人喜欢使用 redux-persist 来实现这一点。

export const useGetUser = () => { //add use infront to make a custom hook
  return useSelector(state => state.gatsByUser) // access user info from redux store
};


export const handleLogin = ({ username, password }) => {
  //suggestion: don't validate password on client side or simply don't use password, 
  //instead use tokens for validation on client side

  if (username === `john` && password === `pass`) {
    dispatch(setUserInfo({
      username: `john`,
      name: `Johnny`,
      email: `johnny@example.org`,
      isLoggedIn: true,
    }));
    return true;
  }

  return false;
};


// adding 'use' infront to make it a custom hook
export const useIsLoggedIn = () => {
  //this will act as a listner when ever the state changes
  return useSelector(state => state.gatsByUser?.isLoggedIn ?? false);
};

export const logout = (callback) => {
  const dispatch = useDispatch(); // redux
  dispatch(clearUserInfo());
};
Run Code Online (Sandbox Code Playgroud)

现在在私人路线上做

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

const PrivateRoute = ({ component: Component, location, ...rest }) => {
  const isLoggedIn = useIsLoggedIn();

  if (!isLoggedIn) {
    return navigate("/dashboard/login");
  }

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

export default PrivateRoute;
Run Code Online (Sandbox Code Playgroud)