It turns out that hosting a next.js application in a subdirectory isn’t as easy as it should be. There are two major issues that I’ve run into when doing this: (1) Handling links to local pages and (2) Handling links to static assets.

The right way to handle this is to set the basePath in the next.config.js file. This will cause the next.js router to prepend the basePath to all links:

/** @type {import('next').NextConfig} */
const nextConfig = {
    ...,
    basePath: '/~davidchan/bair_staging',
};

module.exports = nextConfig;

Unfortunately, there’s still some work to do, as this will only handle the routing. For any links to pages, we need to replace the a tag with the Link tag, which will correctly apply the basePath to the link:

// From:
<a href="/pages"></a>;
// To:
import Link from 'next/link';
<Link href="/pages"></Link>;

The biggest issue, however, is static assets, where the base path isn’t applied (even to components such as the component). There are several cited options which I’ve seen online including assetPrefix (which is ignored in modern next.js), the getRouter().basePath method (which doesn’t work in non-router components), and the getRuntimeConfig().basePath approach (where you specify the basePath in the next.config.js file and then access it via getRuntimeConfig(), which doesn’t work on client-side components). Instead, The best thing that I’ve found is to use addBasePath from next/dist/client/add-base-path.js:

// From:
<img src="something.png" />;
// To:
import { addBasePath } from 'next/dist/client/add-base-path.js';
<img src={addBasePath('something.png')} />;

This works in both client-side and server-side components, and it’s the only thing that I’ve found that works for static assets. It’s a bit of a hack, but it’s the best that I’ve found so far.