Deploying to Github Pages? Don't Forget to Fix Your Links
Is your site not working after you've deployed to Github Pages?
You see a blank page, or maybe the homepage shows up fine, but then you click on a link and nothing happens. You pop up dev inspector only to find a bunch of 404 status codes.
It happens to many developers — the website works fine on localhost but after deploying to Github Pages, everything breaks.
By understanding the core problem, you will be able to spot it and prevent it from happening in all your future deployments, not just on Github Pages.
Let's investigate the issue and make your site work as expected in production.
What are root-relative links?
To better understand why links often break when deploying to Github Pages, first we need to learn what root-relative links are.
Root-relative links[1] are links that start with a forward slash (/
). When clicked, a root-relative link ignores the path location of the current URL. Which part of an URL is the path? Everything that comes after the domain. The path of the following URL, for example, https://example.com/blog/articles
is /blog/articles
.
A root-relative link always leads to the same path within the current domain, regardless of where it is found. Consider the following examples:
<!--
Root-relative links ignore the current URL and always lead
to the same path within the current domain
-->
<!-- Current location: https://example.com/ -->
<a href="/awesome-post"> <!-- leads to: https://example.com/awesome-post -->
<!-- Current location: https://example.com/blog -->
<a href="/awesome-post"> <!-- leads to: https://example.com/awesome-post -->
<!-- Current location: https://example.com/blog/articles -->
<a href="/awesome-post"> <!-- leads to: https://example.com/awesome-post -->
Root-relative links and Github Pages
The inflexibility of root-relative links shows when we move a site to a different path location.
Suppose you have your articles hosted at https://example.com/<article-slug>
and you decide to move them into a dedicated blog section on your website. Your articles are now found at https://example.com/blog/<article-slug>
. If you were linking to another article from that page, say <a href="/awesome-post">
, the link would lead to the old location https://example.com/awesome-post
which doesn't exist anymore.
Root-relative links ignore their current path and they don't follow location changes.
The same thing happens when you develop your site locally and then deploy to Github Pages[2]. The location of your site changes from root (/
) to the project's repository name:
Github Pages hosts your site at a location equal to the repository name
Everything on your site – including your assets such as images, CSS and JS files — is available at the new location. A link pointing to /awesome-article
on localhost needs to lead to /my-project/awesome-article
in production.
Prefix all links
To fix this issue, you have to prefix all links with the repository name of your project. Luckily, most static site generators and frameworks have an option to configure this during the build process so you won't have to do this manually.
Gatsby
In Gatsby, you add a pathPrefix
value to your gatsby-config.js
:
// Configure Gatsby to prefix all links with the Github repository name
module.exports = {
pathPrefix: '/my-project',
}
Then when you build the application, you add the --prefix-paths
flag to the command like so:
gatsby build --prefix-paths
If you don't add this flag, Gatsby will ignore the pathPrefix
configuration and it will build your site as if it was hosted at the root path.
Documentation: https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/path-prefix/
Create React App
With Create React App, the configuration lies in the homepage
property in your package.json
file:
"homepage": "https://username.github.io/my-project",
Using this, Create React App will infer the location to use in the generated HTML.
If you're using React Router v4 or higher, you need an additional configuration on any <Router>
component to prefix your <Link>
s. With <BrowserRouter>
for example:
<BrowserRouter basename={process.env.PUBLIC_URL}>
<Link to="/awesome-post">
</BrowserRouter>
Create React App will extract the path location from the homepage
setting and make it available to you through the PUBLIC_URL
environment variable.
Documentation: https://create-react-app.dev/docs/deployment/#building-for-relative-paths
Next.js
Update: With the release of Next.js 9.5, prefixing all links is much easier than it used to be. You can use the basePath
configuration in your next.config.js
file. If you're using older versions of Next.js, you can follow the steps below.
In Next.js, you first add the assetPrefix
configuration to the next.config.js
file:
const pathPrefix = process.env.NODE_ENV === 'production'
? '/my-project'
: '';
module.exports = {
assetPrefix: pathPrefix,
env: {
pathPrefix,
},
};
Additionally, you also need to assign the prefix to an environment variable so you can reuse it in your application. Unfortunately, Next.js doesn't offer an easy way to add a prefix to your <Link>
components. You'll have to do this manually, and the best way is to create a <PrefixedLink>
component and use it throughout the app.
import Link from 'next/link';
const PrefixedLink = ({ href, as = href, children, ...props }) => (
<Link
href={href}
as={`${process.env.pathPrefix}${as}`}
{...props}
>
{children}
</Link>
);
export default PrefixedLink;
Replace all <Link>
components in your application with <PrefixedLink>
and you should be all set.
Documentation: https://nextjs.org/docs/api-reference/next.config.js/cdn-support-with-asset-prefix
Vue
Prefixing your links with the repository name is quite straightforward in Vue. You need to set the correct publicPath
in vue.config.js
:
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/my-project/' // note the trailing slash
: '/',
};
The setting is applied in production only so it doesn't break your development workflow on localhost.
Documentation: https://cli.vuejs.org/guide/deployment.html#github-pages
Why not use relative links?
You might be wondering — why aren't we using relative links instead? A link such as <a href="awesome-post">
(without the forward slash /
) will respect its current path location and will work even if you host your website several directories deep (/blog/articles/2020/...
).
The downside of using relative links is that they break client-side routing relying on the HTML5 history API. You're probably not using it directly, but React Router, Vue Router, Next.js and Gatsby all use it under the hood. Client-side routing is at the core of single-page applications and it's what makes it possible to navigate to other pages without refreshing the browser.
If your website is not a single-page application and you don't care about client-side routing, Create React App and Vue allow you to opt into relative links.
You'll sometimes see root-relative links wrongly referred to as absolute links. This is technically incorrect because an absolute link always starts with the scheme/protocol (https://). The confusion stems from the fact that root-relative links are absolute with regards to the path section of the URL. ↩︎
Github Pages distinguishes between user sites and project sites. User sites are served from the root path (
/
), but they need to be in a repository named username.github.io. A Github account can only have one user site. That's why you'll more often see project sites and that's what this article refers to. ↩︎