Deploying to Github Pages? Don't Forget to Fix Your Links
Tags: javascript, webdev, beginners, react
Published: 2020-06-25 14:50:00

*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 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:

```html
<!--
	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](https://dev-to-uploads.s3.amazonaws.com/i/7eegh7bdithn4zrio87n.png)

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`:

```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:

```shell
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:

```json
"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:

```jsx
<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](https://nextjs.org/docs/api-reference/next.config.js/basepath) 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:

```js
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.

```jsx
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`:

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

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.

[^1]: You'll sometimes see root-relative links wrongly referred to as absolute links. This is [technically incorrect](https://stackoverflow.com/a/904066/4763270) 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.

## 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 💌](https://maximorlov.ck.page/0076aaf499).