How To Solve Module Not Found Can’t Resolve ‘fs’ in Next.js
While working on custom sitemap functionality for a Next.js project, I encountered the following error: Module not found: Can't resolve 'fs'. While this is a seemingly clear error, the quest for a solution gained me lots of insights.
Next.js is gaining a lot of popularity. For a good reason, it makes developing server-side rendered applications with React a walk in the park. It boasts impressive features, and it works pretty much out of the box.
My personal website utilizes Next.js. One feature that is missing from Next.js is sitemaps, which is understandable because the formation of it depends heavily on the specific data source and other variables.
I decided it was worth it to invest time creating a sitemap for my website; it would most certainly not hurt the SEO. Realizing the sitemap required jumping through hoops. You can utilize the manifest created by the build process of Next.js. I plan on outlining the details in an upcoming post. I came across a peculiar error during the development: The Module not found: Can't resolve 'fs'
.
This error seems straightforward: the filesystem module is not found. Maybe it was not installed or something like that. However, what made this error all the more interesting was that it occurred in the browser. An environment where the filesystem module is not available and most certainly should not be required as a dependency.
Solving the error was a non-trivial task for me at the time, but the process made me better understand how Next.js works under the hood. More specifically, it taught me more about code bundling and splitting. This article explains how to solve the error and highlights the error context, hoping you learn more about Next.js while we are at it.
The error describes a module not being found. What is a module anyway? First, let's review what modules, code bundling and splitting are all about.
Modules and code bundling
Before Next.js or even React were a thing, it was common practice to write all the code for an entire website in one JavaScript file. Dependencies, such as jQuery, were loaded in script tags before this main script was loaded. However, with increasing complexity and ever-growing dependencies, the hassle of tracking and loading dependencies became too much of a burden.
The solution was to split the codebase into modules, which are encapsulated units of JavaScript. For a long time module system were emulated with projects like CommonJS. However, since ES6 modules are native to JavaScipt with import
and export
statements. Most modern browsers support native modules. More can be read about them here.
So by now, we are splitting our codebase into modules and gaining numerous benefits: maintainability and reusability. Most likely, all are modules are in separate files. Are we loading those separately in script
tags? No, of course not! Then we would be back at square one and take a considerable performance hit in the browser.
Now code bundling comes into play. All the modules are bundled into one (or more) files to minimise the number of requests the browser makes while loading a page. In front-end development, when someone speaks of a build step, they refer to the bundling. This step usually involves minification: removing unnecessary characters from source code to reduce the bundle size.
The bundling involves various other tasks usually and can become quite complex. Nowadays most React apps have their files bundled by tools such as Webpack, Rollup or Browersify.
Code splitting
As your codebase grows, the size of your bundle also grows. Now we are facing a problem once again. Increasing bundle sizes results in increased loading time. If there is one thing we want to avoid is slow web pages; we do not want to test our visitors' patience.
To avoid large bundles, we can split the code into multiple bundles. Code-splitting is a feature that is supported by most bundlers. It is one of the most compelling features of these bundlers. You can learn more about Code splitting in React here.
Bundling and splitting in Next
Next.js is a framework that is built on top of React. When we use Next.js, we have a Webpack setup out of the box to bundle our app. In essence, Next.js is a web server running in a Node runtime environment. It serves pages on specific routes, determined by the files in the pages
folder of the Next.js project.
With React, the bundles of JavaScript are loaded by the browser. The rendering is then also done by the browser. With Next.js this rendering is done by the webserver. It renders the page on the server-side and serves the output: HTML.
When you built interactive pages, this HTML still contains multiple references to JavaScript bundles. As you might imagine, not all code that you write for a specific page is needed on all other pages. Loading that code anyway is a waste. This is where code splitting of Next.js kicks in: it creates multiple bundles from one codebase.
This means that each page only loads the JavaScript that is needed on that specific page. This is one of the benefits of using Next.js If a particular page does not use an imported library, it is not included in the JavaScript bundle for that specific page. The client code is automatically broken up by Next.js in several different resources, instead of generating one single file containing all the code.
Within next.js the specific bundles are called chunks. How the chunks are created is determined by a complex code-splitting strategy that is constantly being improved.
Solving the error
Now we understand what code bundling and splitting is. Let's review the issue that caused this investigation in the first place.
The Module not found: Can't resolve 'fs'
error and similar issues most likely occur when you try to import a module that is available on the server-side, but that is not available in the browser. The filesystem module is a prime example of this.
It is perfectly valid to require the filesystem module when you are doing work on the server-side. For example, when fetching data from files on the disk. However, this module is not available in the browser and should not be included in the browser bundle.
Does this mean that you cannot use such Node modules at all? No luckily not, on modern versions of Next.js (9.4+) you can safely use fs
within getStaticProps
or getServerSideProps
, respectively used for static generation and server-side rendering. There is no extra configuration required.
When you only refer to a server-side module in those functions the dependencies are correctly tree-shaken away by a custom Babel plugin that the Next.js team created.
Tree shaking eliminates unused functions from across the bundle by starting at the entry point and only including functions that may be executed. In the case of Next.js, it is extended by removing the dependencies and function used in the getStaticProps
or getServerSideProps files.
When you encounter this issue, the problem can likely be solved by abiding by the above rule.
Code Elimination
Vercel, the team behind Next.js, provides a handy tool to inspect what code is eliminated for the browser bundle visually. You can see that when a function is not referenced in getStaticProps
or getServerSideProps
it will not be eliminated. This can cause the described issue.
Done deal, right? Not so fast. I was still getting the error even when the function that used the filesystem module was only called from the getStaticProps
function. Then, after a period of confusion, it dawned upon me that the method also called itself.
This particular function which was throwing the error was a recursive function. I discovered that such functions also cause this issue to appear. In this case, the initial function call was done in getServerSideProps
. However, it also calls itself recursively, which caused it to be included in the browser bundle.
In that case, you need to add the following code to the next.config.js:
This is also a solution when you are working with older Next.js versions. The configuration essentially tells Webpack to create an empty module for fs, which effectively suppresses the error.
Running code server-side
Other options can be utilised to only run certain code on the server-side. You can check the window property in your code to execute a snippet of code either in the browser or on the server.
You can use the following condition to check whether or not your code is executed on the server-side: if(typeof window === 'undefined')
. Anything inside the scope of this condition is only executed on the server.
This is idiomatic check. If JavaScript is running in a Node environment, several intrinsic objects, such as the window
object, are not available. When we check for the object's existence, we can effectively determine if we are on the server-side or not.
What is even more amazing is that Next.js also removes the code that uses those checks from browser bundles, as a form of build-time optimisation. A browser bundle will not include the content in the scope of the condition.
Conclusion
“It is a painful thing
To look at your own trouble and know
That you yourself and no one else has made it”
So what is the main takeaway? Hopefully, you have learned something about code bundling and splitting, if these concepts were not familiar before.
However, there is a more important lesson to be learned here. I found the solution for the problem with a simple Google search. Yet I did not fully understand the solution and the exact cause of the issue. I could have applied the fix and went on with other things.
Googling for a solution, applying it and moving on without understanding the subtleties or causes of the error is something that software developers do more often than they like to admit, myself included. Sometimes we are on a tight deadline and do not have the time to dig deeper, that is understandable. But if the situation allows for it, we should take a step back and realize that behind each error is a potential treasure trove of knowledge.
An experienced frontend developer most likely would have paid this error no mind. However, I used it to identify a knowledge gap and acted upon it successfully. If you are facing a similar situation, I propose you try the same. Thank you for reading!