Next.js Rewrites, Amazon S3, and Trailing Slashes

Published on 21 December 2020

This post talks about how to set up Next.js rewrites to point to a static site hosted on Amazon S3, in particular how to deal with trailing slashes.

Goal

The problem that I’m describing occurred in the process of creating Memm, a tool for studying for the MCAT.

The goal: move blog.memm.io => memm.io/blog (apparently better for SEO purposes).

  • memm.io: runs on Next.js
  • blog.memm.io: hosted on Amazon S3, using Gatsby

I want memm.io to continue hosting the main site, while only paths under memm.io/blog will actually point to a different underlying endpoint, e.g., http://MY-S3-BUCKET.s3-website-us-west-2.amazonaws.com/.

Problem

When navigating to memm.io/blog and clicking on a link, everything worked fine. However, if the page was refreshed at memm.io/blog/my-slug, it would redirect to memm.io/my-slug. This also meant that linking directly to a blog post would not work, changing the path strangely.

Additionally, my Gatsby had its pathPrefix config set to /blog, which meant there were a variety of static assets that were supposed to be loaded (e.g., http://MY-S3-BUCKET.s3-website-us-west-2.amazonaws.com/foobar.css) that were similarly 404ing due to Next.js’s strange rewriting behavior.

This is a problematic next.config.js (see Next.js documentation for where this goes).

async rewrites() {
  return [
    {
      source: '/blog/:slug*',
      destination: `${BLOG_URL}/:slug*`,
    },
  ];
},


Solution

This leverages a new feature supported in Next 9.5 and above called rewrites.

We’ll explore more in depth why this is happening, but since most people reading articles just want the solution, I fixed it with something like this in my next.config.js

{
  async rewrites() {
    return [
      {
        source: '/blog/:slug*/',
        destination: `${BLOG_URL}/:slug*/`,
      },
      {
        source: '/blog/:slug*',
        destination: `${BLOG_URL}/:slug*`,
      },

    ];
  },
  // Causes next.js to add trailing slashes to end of URLs.
  trailingSlash: true,
}


Using withCss, withSass, withLess

It should also be noted that my next.config.js uses Ant Design, and therefore it has a bit of fiddling with the CSS/Sass/Less loading. In another possible bug with the @zeit/next-css package, trailingSlash does NOT work within the nested withCss call, although strangely rewrites works. This is probably also a bug given that rewrites works but trailingSlash doesn’t, although I didn’t file it – withCss is technically a deprecated package; they shouldn’t be expected to support newer features of Next.js to be compatible with it.

Note how in the code below that rewrites is within the withSass but the trailingSlash must be outside of the withCss call in order to work.

const withCss = require('@zeit/next-css');
const withLess = require('@zeit/next-less');
const withSass = require('@zeit/next-sass');

const BLOG_URL = 'http://MY-S3-BUCKET.s3-website-us-west-2.amazonaws.com';

module.exports = {
  ...withCss(
  withSass({
    ...withLess({
      // Other stuff...
    }),
    // https://nextjs.org/docs/api-reference/next.config.js/rewrites#rewriting-to-an-external-url
    // https://github.com/vercel/next.js/issues/14930
    // In the end, we need this with trailing-slash to work properly. Otherwise
    // it does a 302 and when you refresh blog pages it messes up.
    async rewrites() {
      return [
        {
          source: '/blog/:slug*/',
          destination: `${BLOG_URL}/:slug*/`,
        },
        {
          source: '/blog/:slug*',
          destination: `${BLOG_URL}/:slug*`,
        },

      ];
    },
  })
  ),
  trailingSlash: true,
};


Cause

I’m still not 100% confident in my explanation of the issue, but I think it goes something like this:

Somehow, with the problematic config, this causes two redirects that causes Next.js to remove the /blog/ from /blog/my-slug and simply go to /my-slug. It’s possible this is a bug with Next.js rewrites, but it could also be an intended interaction given the quirks of the two opposing redirect rules.

Comments