DEV Community

Cover image for Deploying a Next.js app to GitHub Pages
James Wallis
James Wallis

Posted on • Edited on • Originally published at wallis.dev

Deploying a Next.js app to GitHub Pages

This blog is part of a series where I document rebuilding a website that relies on HTML, CSS and Bootstrap in React.js using the Next.js framework to improve performance, reduce costs and increase my workflow for future changes.

2021 Update
I have moved Wallis Consultancy from GitHub Pages to Vercel. I wrote a post describing my motivations for doing so which you can read here. Essentially, Next.js integrates with Vercel a lot better than with GitHub Pages.
I've kept a version of Wallis Consultancy hosted on GitHub pages for this blog and have updated all links to Wallis Consultancy below.
Having said that, GitHub Pages is 100% still a good place to host your Next.js project!

The finished website (hosted on GitHub Pages): https://james-wallis.github.io/wallisconsultancy/
The source code: https://github.com/james-wallis/wallisconsultancy

 Intro

The re-implementation of Wallis Consultancy into a Next.js application is complete. This blog post documents the process of taking a Next.js project and hosting it on GitHub pages. It covers:

  • Using next export to convert the Next.js project to a static website.
  • Building a Travis pipeline to build the website and push it to a gh-pages branch.

Overview of technologies

GitHub Pages

GitHub Pages is a static site hosting service that takes HTML, CSS, and JavaScript files straight from a repository on GitHub optionally runs the files through a build process and publishes a website.

GitHub Pages

Travis

Travis CI is a hosted continuous integration service used to build and test software projects hosted at GitHub and Bitbucket.

It’s free for open-source projects and integrates automatically with Github. All you need to do is sign up and add a .travis.yml file and it’s ready to go.

Travis CI

Next.js export

next export allows you to export your app to static HTML, which can be run standalone without the need of a Node.js server.

It generates the HTML into an out directory. From there you can use tools such as serve to run your app.

Now that the technologies used in this blog have been introduced, let's deploy our Next.js app to GitHub Pages.

Creating the Travis build

Connecting Travis to a GitHub repository is as simple as creating a .travis.yml. The following documents this process and how to use secret environment variables with a Travis build.

  1. Create a .travis.yml file in the top directory of your Github repository.
  2. Add the following (without the comments):
language: node_js # Node.js based project
node_js:
  - 12 # Level of Node.js to use
cache:
  directories:
  - node_modules # Cache the node_modules folder for quicker build times
script:
  - npm run build # Runs next build
  - npm run export # Runs next export and generates the out directory
  - touch out/.nojekyll # Creates a file telling Github not to build the project using Jekyll
deploy:
  provider: pages # Informs Travis this is a deployment to GitHub Pages
  skip_cleanup: true # Prevents Travis from resetting the working directory made during the build
  github_token: $github_token # GitHub access token to use when pushing to the gh-pages branch
  local_dir: out # Directory to push to the gh-pages branch
  on:
    # Only deploy when the build is on master or main branch - two common default branch names
    # If you're using a different branch name, add it here
    all_branches: true
    condition: $TRAVIS_BRANCH =~ ^(master|main)$

Enter fullscreen mode Exit fullscreen mode

For more information the official Travis Github Pages docs

  1. Once you’ve added the .travis.yml to your repository, you need to add the github_token (needed to push to your gh-pages branch) variable to your Travis CI settings.

    1. First get an API token following the instructions on Creating a personal access token - GitHub Docs Note: As my repository was private while making this blog I enabled the whole repo scope. However you may be able to just enable the public_repo scope. GitHub Repo Scope for access token The Full GitHub repo scope
    2. Open https://travis-ci.com/github/{your_username}/{your_repository} in a browser.
    3. Navigate to more options -> Setting. Travis Settings Travis Settings
    4. Once there add a new environment variable called github_token and use your access token as the value. Optionally make it only available on the master branch. Travis Settings Environment Variable Travis Settings Environment Variable
  2. Now that you've set up the Travis settings and .travis.yml you're ready to start your first Travis build. To do this, publish your new .travis.yml to your master branch and it will start automatically. If you’ve already done this, start a new build of master from the Travis-ci UI.

Phew, that was a lot of configuring, but it's done. Let's set up GitHub Pages so that the website will be viewable.

Setup GitHub Pages

By this point, the Travis build should have successfully completed and created a gh-pages branch in your repository. This means that the static website code is available and just needs to be served somewhere such as GitHub Pages.

GitHub branch overview
You should be able to see the gh-pages branch.

To enable GitHub Pages for your repository you need to:

  1. Navigate to the settings tab for your Github repository (such as https://github.com/james-wallis/wallisconsultancy/settings)
  2. Scroll down to the “GitHub Pages” section.
  3. Under the source tab select gh-pages branch GitHub Pages settings The GitHub Pages settings

In a little while, you should be able to access your website at the URL provided by GitHub (if you can’t go back over the Travis-CI steps above). That's all the setup that is needed to host a static site with GitHub pages.

Or is it...

Something isn't quite right... where's the CSS styling

If you followed both sections above you will be expecting to see your website as it looked on your local machine.

Instead you will likely be greeted with a website with the correct content, but no styling. Additionally, if you try to navigate between pages, they will not resolve. It'll look something like the below:

Broken Wallis Consultancy site
Wallis Consultancy website without the CSS

Why is this happening you ask?
Next.js expects the CSS, JavaScript files and Images to be hosted on user.github.io/ but in the case of GitHub pages, the site will be hosted on a subpath, in my case user.github.io/wallisconsultancy. This results in the website not being able to find any of its dependencies or link to other pages.

You can recreate this locally by running next export and then using serve to serve the parent directory of your output directory (usually out). So for me serve wallisconsultancy where the output directory is wallisconsultancy/out.

Ok fine, but can we fix it?

Yes of course!

Note: If you’re going to host on a custom domain this problem will disappear (as long as you are not using a subpath like GitHub pages). Skip the rest of this blog and read my next blog: Using a custom domain with GitHub Pages.

 Next.js assetPrefix and basePath to the rescue

This next section will be split into two subsections. The first will focus on fixing the CSS styling and other assets such as images using assetPrefix. The second will focus on fixing links to different pages, first using an environment variable to prefix the route and secondly using basePath, a new configuration variable introduced in Next.js 9.5.

Fixing CSS and other assets

Fixing CSS and other assets is simple and can be done in only a few steps:

  1. Open or create a next.config.js file.
  2. Add an assetPrefix to your module.exports with the value of your GitHub pages subpath with a forward slash at either side. For me this is:
module.exports = {
    assetPrefix: '/wallisconsultancy/',
}
Enter fullscreen mode Exit fullscreen mode

With that simple change, you should be able to push that change to GitHub pages and will be able to see the page layout that you expect.

Fixing Links between pages

Next.js 9.4 and below
Prior to Next.js 9.5, fixing the page links meant modifying each <Link> that you had created to have a prefix. The cleanest way of achieving this is to:

  1. Open or create a next.config.js file.
  2. Add an environment variable called BACKEND_URL with the value of your GitHub Pages subpath with a forward slash at the start. For me this is:
module.exports = {
    env: {
        BACKEND_URL: '/wallisconsultancy',
    },
}
Enter fullscreen mode Exit fullscreen mode
  1. Modify your <Link> components to use the prefix by changing them to be:
<Link href={`${process.env.BACKEND_URL}${href}`}>{href}</Link>
Enter fullscreen mode Exit fullscreen mode

So for a link to the about page the href for the <Link> would change from

href="/about"
Enter fullscreen mode Exit fullscreen mode

to

href={`${process.env.BACKEND_URL}/about`}
Enter fullscreen mode Exit fullscreen mode

This is a bit messy, but fortunately, in Next.js 9.5 this was simplified with the introduction of a basePath variable.

Next.js 9.5 and above
Instead of adding a BACKEND_URL to every <Link>, Next.js 9.5 introduces the basePath variable. To use it all you need to do is:

  1. Open or create a next.config.js file.
  2. Add a basePath to your module.exports with the value of your GitHub pages subpath with a forward slash at the start. For me this is:
module.exports = {
    basePath: '/wallisconsultancy',
}
Enter fullscreen mode Exit fullscreen mode

Final next.config.js

Combining the assetPrefix and basePath my next.config.js is:

module.exports = {
    basePath: '/wallisconsultancy',
    assetPrefix: '/wallisconsultancy/',
}
Enter fullscreen mode Exit fullscreen mode

Bonus: With next-optimized-images
In a previous blog post I introduced next-optimized-images which can be used to improve the performance of a website by compressing the images.

To fix the GitHub Pages subpath issue with it I added the imagesPublicPath variable to my next.config.js. With this fix it now looks like this:

const withPlugins = require('next-compose-plugins');
const optimizedImages = require('next-optimized-images');
module.exports = withPlugins([
  [optimizedImages, {
    mozjpeg: {
      quality: 80,
    },
    pngquant: {
      speed: 3,
      strip: true,
      verbose: true,
    },
    imagesPublicPath: '/wallisconsultancy/_next/static/images/',
  }],
  {
    basePath: '/wallisconsultancy',
    assetPrefix: '/wallisconsultancy/',
    env,
  },
]);
Enter fullscreen mode Exit fullscreen mode

And with that, my website is hosted on GitHub pages, looks good and I can navigate between pages as I expect. You are now able to show your website to anyone around the world!

Here's the link to Wallis Consultancy again to see the result of the above steps!

Want to use a custom domain?

Roundup

In this blog, I demonstrated how to build a Travis build that will build and export your Next.js application into a static website. I then configured GitHub pages to host the website and fixed CSS and link problems due to the subpath it hosts websites on.

In the next and final blog of this series, I'll show you how to use a custom domain with GitHub Pages.

Top comments (12)

Collapse
 
ottobonn profile image
Travis Geis • Edited

Thanks for the guide! I also had trouble with CSS, but it was because by default Github Pages ignores the _next directory because it assumes names prefixed with underscore are Jekyll files. You can add a .nojekyll file to the root of your Next public directory (so it ends up in the GH Pages root) to disable Jekyll processing.

I use gh-pages locally to deploy to Github, and to get that to deploy the .nojekyll dotfile, you also have to add the --dotfiles option to it. So in my package.json, my deploy script ends up being "deploy": "gh-pages --dotfiles --dist out".

Cheers!

Collapse
 
jameswallis profile image
James Wallis

Good to know! Thanks Travis.

Collapse
 
nikacodes profile image
Nicole Zonnenberg

Hi James!

I was able to follow your walk through pretty cleanly. However, it seems to have published my readme to my github pages instead of my nextjs app. Do you have any insight?

Collapse
 
jameswallis profile image
James Wallis • Edited

Hi Nicole! I just had a look at your repository and it looks like you're using the main branch whereas your .travis.yml is saying to only deploy the Next.js application on master branch. Could you try changing this line: github.com/Sun-Mountain/Sun-Mounta... to be main instead of master.

Then I think you'll need to change the GitHub pages source to be the gh-pages branch (which also hasn't been created yet due to the issue above).

Let me know if that helps and I'll update the tutorial.

Thanks!

Collapse
 
nikacodes profile image
Nicole Zonnenberg

Hi James!

Thank you for your prompt reply! And thank you for pointing out the main, it's a new change so I'm still getting used to it. 🤦‍♀️

And it worked! Thank you so much! <3

Thread Thread
 
jameswallis profile image
James Wallis

Good to hear, your website looks great!

Thread Thread
 
nikacodes profile image
Nicole Zonnenberg

Thank you! And one more quick question. For updating the website, would you recommend branching off of master/main and then merging main into gh-pages? Or branching of gh-pages and merging into gh-pages and then main?

Thread Thread
 
jameswallis profile image
James Wallis • Edited

So you should branch off master/main and then merge back into master/main.
Once you've merged your changes, Travis will build and export your Next.js application and then push the new HTML (out directory) onto your gh-pages branch - it uses a force push to completely overwrite the branch.

You shouldn't ever need to touch the gh-pages branch.

Collapse
 
jasonzhouu profile image
周宇盛 Yusheng Zhou

The image in the right side doesn't show appropriately.
james-wallis.github.io/wallisconsu...

Collapse
 
jameswallis profile image
James Wallis

Thanks for pointing it out! I'll take a look soon - it is likely something to do with my Netlify CMS configuration.

Collapse
 
muhammedmoussa profile image
Moussa

thank you man, I followed your post, and it all good!

Collapse
 
jameswallis profile image
James Wallis

Cheers Moussa! Thanks for the feedback