GitHub Pages 重定向的实现

GitHub Pages没有开放可配置的服务器端301、302重定向,只能用其他替代方式实现页面重定向。Cloudflare免费版的重定向规则也受10条的上限限制。我们可以采用以下几种跳转策略(这些方法同样适用于其他缺乏路由控制的静态托管服务):

场景一:从本页跳转到任意URL

小明在多个平台上发布文章,包括自己的博客、微博、知乎等。为了方便访客找到所有内容,他希望将这些文章整合到自己的GitHub Pages。小明只需为每个外部文章创建一个重定向页面,并将该页面的 URL 指向原始文章,即可将外部文章加入网站的文章列表中。访客访问重定向页面时会被自动引流到原始文章。如果使用了Jekyll等静态网站生成器,这些重定向页面还能享受到自动索引功能。

HTML文件通用方法

由于 GitHub Pages 等静态托管平台无法像传统服务器那样返回 301/302 HTTP 状态码,这些中间跳转页在服务器端依然属于 200 OK 的普通网页。因此,在编写 HTML 源码时,建议组合使用以下技术:

对搜索引擎的爬虫来说,js 跳转的效果可能不是那么稳定1,因此一般不建议单独使用,建议至少结合 HTML 标签以确保兼容性。同时,这类静态跳转无法像 301 重定向那样自动传递权重,因此必须搭配 noindex 标签来规范爬虫的行为,确保搜索引擎只收录原始文章。

Jekyll模板跳转

我的Jekyll主题less-style-please内置了重定向页面的模板,在需要跳出的文章头部声明:

---
layout: redirect
redirect_to: /newdir/newpage
---

即可自动调用上述三种跳转方式:

  1. 利用meta刷新标签重定向

  2. 利用javascript重定向

  3. 在正文前渲染一个指向所设定URL的超链接。即使前两种方式都失效,用户依旧可以手动点击

具体实现上,meta刷新与nofollow标签通过修改 /_includes/head.html 实现,js跳转与添加a标签通过修改 /_layouts/redirect.html 实现。

场景二:从站内旧URL跳转到新页

小明为了更好地组织内容,决定对部分博客文章重命名。但他此前已经在其他平台发布了指向旧 URL 的链接。由于无法修改外部链接,他需要一种机制确保访客点击旧链接后依然能访问文章内容。

Jekyll Redirect From插件

Jekyll官方有一款利用meta刷新标签跳转的插件Jekyll Redirect From,在新文章头部声明:

---
redirect_from: /olddir/oldpage
---

按理声明后即可实现自动跳转。但不知是插件本身的bug还是GitHub Pages权限问题,经过GitHub Actions在线编译后,插件的确在旧URL位置自动生成了HTML文件,但头部并未出现原本应自动生成的meta刷新标签。

目前这种方法仍需结合前文方式修改Jekyll模板的 /_includes/head.html ,才能在GitHub Pages实现自动跳转。

场景三:目录重命名后的批量重定向

小明为了更好地组织内容,将部分目录进行了重命名。例如,将名为 "aaaa" 的目录重命名为 "bbbb"。这导致了目录下所有网页的 URL 也发生了变化。单页跳转相对容易配置,但如果涉及整个目录的结构调整,逐一修改将是一项耗时费力的工作。此时可以采用以下两种批量处理方案。

利用404页面降级跳转

在代码库根目录建立一个 404.html,利用js跳转来曲线救火:

window.onload=function() {
    var url = window.location.href
    switch (true) {
        case url.includes('/aaaa'):
            window.location.replace(url.replace('/aaaa', '/bbbb'))
            break
    }
}

以后每次改目录名,只需在脚本中新增一个case。这种方法的缺点是对搜索引擎爬虫不太友好,且访客会短暂看到404页面,体验存在割裂感。

利用 Service Worker 客户端拦截

为了解决404页面的延迟感并提升体验,可以引入 Service Worker。Service Worker 可以在浏览器端拦截网络请求,相当于在访客本地建立了一个代理。当访客请求旧目录下的 URL 时,Service Worker 会截获该请求并直接返回重定向响应。

在你的 Service Worker 脚本(如 sw.js)中监听 fetch 事件:

self.addEventListener('fetch', event => {
    const url = new URL(event.request.url);
    if (url.pathname.startsWith('/aaaa/')) {
        const newUrl = url.href.replace('/aaaa/', '/bbbb/');
        event.respondWith(Response.redirect(newUrl, 302));
    }
});

优点:生效后,跳转在客户端无感完成,无需经过404页面,过渡非常平稳。

缺点:访客必须至少成功访问过一次网站,触发 Service Worker 的注册与激活,拦截才能生效。因此,它对初次访问的搜索引擎爬虫依然无效。通常建议与方案 A(404跳转)结合使用,以兼顾用户访问体验与基础页面的可用性。