刷新后 Github 页面 URI 上的客户端路由和 404

Client-side routing and 404 on Github Pages URIs after refresh

这可能更多的是 Github 页问题而不是 JS 问题。

我正在学习 VanillaJS SPA. I'm finding when I push it to Github Pages with gh-pages 上的教程,而如果您从 / 开始并单击各种导航链接,则路由有效,您将看到 URI 相应地更新为 /posts , /settings, 等等

但是,如果我在 /posts/settings 等刷新页面,或尝试转到 / 以外的任何路线,我会收到来自 Github.

我敢肯定,这是因为应用程序只是在 DOM 操作并更新浏览器中的 URI 字符串。 Github 页面的网络服务器知道如何处理 /,但当涉及到 /posts/settings 等时,它正在寻找实际的子目录,这些子目录实际上并不存在于外部DOM 操纵。

我很好奇是否有办法让客户端路由与 Github 页面一起工作,或者这是否行不通并创建子目录以对应这些路线是必经之路?

这是路由代码:

import Dashboard from "./views/Dashboard.js";
import Posts from "./views/Posts.js";
import PostView from "./views/PostView.js";
import Settings from "./views/Settings.js";

const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\/").replace(/:\w+/g, "(.+)") + "$");

const getParams = match => {
    const values = match.result.slice(1);
    const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);

    return Object.fromEntries(keys.map((key, i) => {
        return [key, values[i]];
    }));
};

const navigateTo = url => {
    history.pushState(null, null, url);
    router();
};

const router = async () => {
    const routes = [
        { path: "/", view: Dashboard },
        { path: "/posts", view: Posts },
        { path: "/posts/:id", view: PostView },
        { path: "/settings", view: Settings }
    ];

    // Test each route for potential match
    const potentialMatches = routes.map(route => {
        return {
            route: route,
            result: location.pathname.match(pathToRegex(route.path))
        };
    });

    let match = potentialMatches.find(potentialMatch => potentialMatch.result !== null);

    if (!match) {
        match = {
            route: routes[0],
            result: [location.pathname]
        };
    }

    const view = new match.route.view(getParams(match));

    document.querySelector("#app").innerHTML = await view.getHtml();
};

window.addEventListener("popstate", router);

document.addEventListener("DOMContentLoaded", () => {
    document.body.addEventListener("click", e => {
        if (e.target.matches("[data-link]")) {
            e.preventDefault();
            navigateTo(e.target.href);
        }
    });

    router();
});

我没有使用这个 server.js 因为我没有看到 express 如何与 Github 页面一起工作:

const express = require('express');
const path = require('path');

const app = express();

app.use(
  '/static',
  express.static(path.resolve(__dirname, 'frontend', 'static'))
);

app.get('/*', (req, res) => {
  res.sendFile(path.resolve(__dirname, 'frontend', 'index.html'));
});

app.listen(process.env.PORT || 8000, () => console.log('Server running...'));

GitHub 页面仅适用于静态网站。这意味着它不提供设置服务器(您可以在其中使用 Node.js/Express 代码)或允许动态页面路由的选项。

但是,它确实允许您拥有自定义 404 页面,您可以将其用作解决此问题的方法:

将您的主要逻辑移动到一个名为 404.html 的文件中,然后每次用户请求的路径实际上不存在时都会提供该文件。

这种方法的一个缺点是您无法限制网站上的范围,因此您必须手动为真正找不到的路径创建一个特殊情况。

您不能使用 URL 导航,但必须实施 SPA # 标签导航。
GitHub 页面将仅支持 Jekyll,而不是 URL 类似 Firebase 托管的重写。

只需编辑 index.htmlroutes:

<nav class="nav">
    <a href="#" class="nav__link" data-link>Dashboard</a>
    <a href="#posts" class="nav__link" data-link>Posts</a>
    <a href="#settings" class="nav__link" data-link>Settings</a>
</nav>

点击这些将发出 popstate 事件。

如果您将 404.html 添加到 /docs 文件夹并将 html 从 index.html 文件复制并粘贴到其中,这将解决任何构建问题。

步骤

  • 构建应用程序
  • 复制并粘贴 index.html 到一个新的 404.html 文件中
  • 推送给你GitHub repo 以便页面可以重新部署网站的新版本

注意:每次都必须这样做,因为构建会从 /docs 文件夹中清除创建的 404.html 文件。这适用于 angular 个应用程序。