So next.js is supposed to be for building static websites, so including static files in the build should be easy, right?
Unfortunately, it's actually quite hard.
Next.js is great for generating static websites and doing it with either the Pages Router or the App Router is pretty simple. For the sake of this article, we'll be using the App Router.
Most docs and articles will tell you to do something along the lines of:
This works great and you'll be able to generate static pages really easily!
Now lets say you want to build and Server Component that uses a static file at runtime. When this is deployed the source files aren't deployed, it's deployed as build output. This means none of the source files are going to be where you expect them to be!
In the above example we use
fast-glob to find all the files in the
That directory doesn't exist in the build output, so when you go to run the code on your website it will fail.
All the the docs and answers on StackOverflow will tell you to put your static files in the
You could put them there but:
- They'll actually be public and hosted on your website
- If you're using
@next/mdxyou have to put the files in specific directories, none of which are
Given point 2 the
public directory is out of the question.
We would have to represent the files in the
public directory in the
Maybe you could write some code to copy the files into the public directory, but that adds a lot of complexity to the code and build process.
The key to solving this is realizing that everything we're doing above is leaning into node to load files. When you're building a next.js app you're building on top of a bundler, and a bundler's main job is including files in your website!
In webpack imports have a lot of power.
Most people only use the
import keyword to import a single file, but webpack has a feature that lets you gather a list of files and then import them dynamically as needed.
This API is called
require.context and it's pretty powerful.
Now since we're using bundler APIs to include the files they will get included in the bundle that
In the above example we're importing a
When we do imports like this all the loaders in our
webpack config will be applied to the file.
Sometimes you might want this, like if you're actually importing some code you want transformed, but in our case we just want the raw file content.
We could configure webpack somehow to do something special for
.mdx files in this case, but that would be a lot of work and prone to breaking things.
Instead we can can lean into another lesser known webpack feature called "Inline Loaders".
Modifying the above example we can use the
raw-loader to get the raw file content.
For my use case I also wanted to inject some git data about the file at build time. This is the exact same problem, when deployed the git data won't be available.
To solve this we can replace the
raw-loader with a custom loader that will inject the git data at build time and include the source.
All we have to do is update the inline loader with a relative path to our custom loader.
Bundlers can do a whole lot. Learning how to control them can be a bit daunting, but once you do you can do some pretty cool stuff!