DEV Community

Cover image for Deploying to Github Pages? Don't Forget to Fix Your Links
Maxim Orlov
Maxim Orlov

Posted on • Updated on • Originally published at maximorlov.com

Deploying to Github Pages? Don't Forget to Fix Your Links

This article was originally published a day earlier at https://maximorlov.com/deploying-to-github-pages-dont-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 links1 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 -->
Enter fullscreen mode Exit fullscreen mode

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 Pages2. 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',
}
Enter fullscreen mode Exit fullscreen mode

Then when you build the application, you add the --prefix-paths flag to the command like so:

gatsby build --prefix-paths
Enter fullscreen mode Exit fullscreen mode

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",
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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,
  },
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
    : '/',
};
Enter fullscreen mode Exit fullscreen mode

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.

React Router v6, which at the time of writing is still in alpha, is expected to support relative links together with client-side routing. That's great news because it means we'll get portable apps out of the box and we won't have to worry about broken links anymore.

Become a skilled Node.js developer

Every other Tuesday I send an email with tips on building solid Node.js applications. If you want to grow as a web developer and move your career forward with me, drop your email here 💌.


  1. 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. 

  2. 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. 

Top comments (4)

Collapse
 
evert profile image
Evert Pot

Github pages is Jekyll though!

Collapse
 
ambroseus profile image
Eugene Samonenko

how about html base tag?
w3schools.com/tags/tag_base.asp

Collapse
 
maximization profile image
Maxim Orlov

Downside of base tag is that it breaks anchors. Gatsby decided to not use them because of that.

Collapse
 
ambroseus profile image
Eugene Samonenko

omg.. forgot about anchors.. many thanks for the explanation ☝️🤝