Hi, I'm Andrew Lisowski

Getting next.js to work on a domain at anything other than the root takes a little extra configuration. This post will walk you though the steps to get it up and running.

Prefix Static Assets

Github project pages urls are deployed to https://your-username.github.io/your-project. To get next.js to properly serve all of your assets (js, css, html) your must include a assetPrefix in your next.config.js.

const debug = process.env.NODE_ENV !== 'production';
const assetPrefix = debug ? '' : '/your_prefix/';

module.exports = {
  assetPrefix
};

Now you will find that when you deploy your project website to github pages all the static assets work, but if you try clicking anything using the @next/link component you'll get a 404. If you have any pages with images served from the static folder you will also notice that they do not work either!

Prefix URLs

So it seems to get our github project page to work we'll also need to all the URLs in our app to be prefixed. This includes both anchor hrefs and img srcs. You could do this yourself, or you could install next-prefixed a package I published to address this issue.

Configure

First install next-prefixed and add the following to your next.config.js.

const debug = process.env.NODE_ENV !== 'production';
const assetPrefix = debug ? '' : '/your_prefix/';

module.exports = withPlugins([withCSS, withMDX, withBlog], {
  assetPrefix,
  publicRuntimeConfig: {
    assetPrefix
  }
});

publicRuntimeConfig will expose the assetPrefix to our next.js app.

Usage

Now that you have fully configured your assetPrefix all thats left to do is switch out all @next/link components and img tags with the prefixed versions.

import { Link, Image } from 'next-prefixed';

const Example = () => (
  <div>
    <Image src="/static/jimp.png" alt='Code example' />
    <Link href="/blog" />
  </div>
);

These components use assetPrefix to prefix all the URLs so they resolve in your github-pages app correctly. Now your project page links work!

Manual Prefix

If you need to manually prefix some URLs that aren't covered by next-prefixed's components, it also exports prefixUrl. This function simply joins the assetPrefix with whatever path is supplied to it.

import { prefixURL } from 'next-prefixed';

prefixURL('/blog'); // => my_prefix/blog

All done!

Now your github project page should work with next.js without a flaw!

This post aims to outline the steps one must take to publish and manage a Github Pages website for your user account.

Goals:

  • Host source code on Github
  • Use your-username.github.io to host the page
  • Use custom domain

This website was made using the following technique.

1. Setup source repository

Set up a repository that contains the files used to build your website. It doesn't really matter how you build it but it must output a directory with all the static files needed to serve your website. For this post I will be using next.js as an example static website generator.

2. Setup github.io repository

This repository will be used for hosting your build website. This is what will be served on your-username.github.io.

Set it as a remote for you source code repository:

git add remote github-io https://github.com/your-username/your-username.github.io

3. Install push-dir

We are going to need to push our project to github somehow. I've found that push-dir is a great tool with a simple interface.

yarn add push-dir

4. Setup deployment script

Add a deploy script to your project.

{
  "scripts": {
    "deploy": "next build && next export && touch out/.nojekyll && push-dir --dir=out --branch=master --cleanup --verbose --remote=github-io"
  }
}

Important steps:

build

You must first build your website in some way. Here we run next build and next export.

.nojekyll

Since the next.js website creates some folder with _ in the name we need to include a .nojekyll. If you build process produces folder or files with underscore you need to do this too.

push-dir

Finally this is where the magic happens. After we have built the website we use push-dir to push the output directory to the github-io's master branch.

Now we have successfully published to your-username.github.io

5. Setup custom domain

Follow this guide on how to add the correct IPs to your DNS.

Once you have done that we need to generate a CNAME file for our custom domain and make sure that it gets included in the out directory that is pushed to github-io.

Add the following to scripts (for next.js we put the CNAME in the out directory):

{
  "scripts": {
    "create:cname": "touch out/CNAME && echo "your.com" >> out/CNAME",
    "deploy": "next build && next export && && npm run create:cname && touch out/.nojekyll && push-dir --dir=out --branch=master --cleanup --verbose --remote=github-io"
  }
}

Enable HTTPS on Github

Go to the settings page for your your-username.github.io repository and check the Enforce HTTPS option.

All done!

That's it! Now you have a static website managed through github with a custom domain

Images on the webpack have a few drawbacks that take some programming to get around.

  • Height and width are unknown — this can cause page jumps and weird layouts
  • Blank space while loading
  • Large images are slow on old devices or weak internet connections
  • The HTML page will download all the images on page load — this can be super slow if you have a lot of images on one page

Luckily there have been a bunch of web standards made to solve these problems. But navigating them all them and then putting it all together can be daunting.

Enter React-Ideal-Image. It is a react component that has a bunch of cool functionality built in. Such as: LQIP, WebP, SrcSet, and other UX/features to help you on slow devices. You can read more about all the cool things that went into the component here. But one problem still remains when using this component, how do you generate all the necessary images to make this component really shine?

Loading Images with Webpack

You probably guessed it by the title, the way we can achieve all this cool functionality is by using Webpack and a few loaders to transform our images at build time!

First here is a simple configuration that will load the images during the build.

module.exports = {
  module: {
    rules: [
      {
        test: /.(gif|png|jpe?g)$/i,
        use: ['file-loader']
      }
    ]
  }
};

And here is an image rendered with React-Ideal-Image. (right now it doesn’t do anything too cool).

import IdealImage from 'react-ideal-image';
import image from './goat.png';
const ImageComponent = props => {
  <IdealImage
    {...this.props}
    src={image}
    width={1786}
    height={1540}
    placeholder={{ lqip: image }}
    srcSet={[{ width: 1786, src: image }]}
  />;
};

Okay, so we have an image working and rendering, but we aren’t using any of the cool features! You might notice that we are just using the image for the LQIP image. This doesn’t get us anything and will probably make our app al little slower.

LQIP Loader

LQIP Loader will take any image and produce a Base64 image string and include it with the src any time you import an object, pretty cool! Let’s see what our config looks like now.

{
  test: /.(gif|png|jpe?g)$/i,
  use: [
    'lqip-loader'
    'file-loader'
  ]
}

Now in our component when we import an image, we get an object with a path to the original src and the LQIP Base64 string.

import IdealImage from 'react-ideal-image';
import image from './goat.png';
const ImageComponent = props => {
  <IdealImage
    {...this.props}
    src={image.src}
    width={1786}
    height={1540}
    placeholder={{ lqip: image.preSrc }}
    srcSet={[{ width: 1786, src: image.src }]}
  />;
};

Now as we scroll down the page, React-Ideal-Image will show us the initial LQIP image:

LQIP Image

And then once the actual image has downloaded it will show us the real image. So cool!

Actual Image

But this image is HUGE! Slow devices will have trouble even with LQIP enabled. And we still have the height and the width hard-coded in the component, not good.

Generating srcset

srcset allows use to tell the browser about multiple sizes of our image and it decided which on to load based on the size of the device. To accomplish this we need another loader. For this we will use responsive-loader since it has a simple API and works with the LQIP loader.

We do not need the file-loader anymore because the responsive-loader does this for us. Here I include a few sizes that should cover most devices.

{
  test: /.(gif|png|jpe?g)$/i,
  use: [
    'lqip-loader'
    {
      loader: 'responsive-loader',
      options: {
        sizes: [300, 600, 900, 1200]
      }
    }
  ]
}

Now we can pass the generated srcset to React-Ideal-Image. The browser will load the smallest size image suitable for the device.

import IdealImage from 'react-ideal-image';
import image from './goat.png';
const ImageComponent = props => {
  <IdealImage
    {...this.props}
    // responsive-loader puts its data in an object
    src={image.src.src}
    width={image.src.height}
    height={image.src.width}
    placeholder={{ lqip: image.preSrc }}
    srcSet={image.src.images.map(i => ({
      ...i,
      src: i.path
    }))}
  />;
};

We’re almost done! All that’s left is to optimize it a little bit.

Optimization

Some images can be huge but contain very little data. Minifying our images might be able to save us from transferring needlessly large files to the client. To accomplish this lets use image-webpack-loader, which uses imagemin to make the smallest images possible.

{
  test: /.(gif|png|jpe?g)$/i,
  use: [
    'lqip-loader',
    {
      loader: 'responsive-loader',
      options: {
        sizes: [300, 600, 900, 1200],
        name: '[path][name]-[width].[ext]'
      }
    },
    'image-webpack-loader'
  ]
}

In practice I was able to see some of my images reduce up to 85% in size!

Conclusion

You can make this component a little more generic and not just render an image of a goat, but anything you pass in. This image is far more useful to the user and creates a smoother loading experience on a range of devices