Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Module not found: Can't resolve 'fs' in Next.js application

Tags:

Unable to identify what's happening in my next.js app. As fs is a default file system module of nodejs. It is giving the error of module not found.

enter image description here

enter image description here

like image 360
Ibad Shaikh Avatar asked Nov 20 '20 08:11

Ibad Shaikh


2 Answers

If you use fs, be sure it's only within getInitialProps or getServerSideProps. (anything includes server-side rendering).

You may also need to create a next.config.js file with the following content to get the client bundle to build:

For webpack4

module.exports = {   webpack: (config, { isServer }) => {     // Fixes npm packages that depend on `fs` module     if (!isServer) {       config.node = {         fs: 'empty'       }     }      return config   } } 

For webpack5

module.exports = {   webpack5: true,   webpack: (config) => {     config.resolve.fallback = { fs: false };      return config;   }, }; 

Note: for other modules such as path, you can add multiple arguments such as

{   fs: false,   path: false } 
like image 163
Arjun Kava Avatar answered Sep 28 '22 20:09

Arjun Kava


Minimal reproducible example

A clean minimal example will be beneficial to Webpack beginners since auto splitting based on usage is so mind-blowingly magic.

Working hello world baseline:

pages/index.js

// Client + server code.  export default function IndexPage(props) {   return <div>{props.msg}</div> }  // Server-only code.  export function getStaticProps() {   return { props: { msg: 'hello world' } } } 

package.json

{   "name": "test",   "version": "1.0.0",   "scripts": {     "dev": "next",     "build": "next build",     "start": "next start"   },   "dependencies": {     "next": "12.0.7",     "react": "17.0.2",     "react-dom": "17.0.2"   } } 

Run with:

npm install npm run dev 

Now let's add a dummy require('fs') to blow things up:

// Client + server code.  export default function IndexPage(props) {   return <div>{props.msg}</div> }  // Server-only code.  const fs = require('fs')  export function getStaticProps() {   return { props: { msg: 'hello world' } } } 

fails with:

Module not found: Can't resolve 'fs'  

which is not too surprising, since there was no way for Next.js to know that that fs was server only, and we wouldn't want it to just ignore random require errors, right? Next.js only knows that for getStaticProps because that's a hardcoded Next.js function name.

OK, so let's inform Next.js by using fs inside getStaticProps, the following works again:

// Client + server code.  export default function IndexPage(props) {   return <div>{props.msg}</div> }  // Server-only code.  const fs = require('fs')  export function getStaticProps() {   fs   return { props: { msg: 'hello world' } } } 

Mind equals blown. So we understand that any mention of fs inside of the body of getStaticProps, even an useless one like the above, makes Next.js/Webpack understand that it is going to be server-only.

Things would work the same for getServerSideProps and getStaticPaths.

Higher order components (HOCs) have to be in their own files

Now, the way that we factor out IndexPage and getStaticProps across different but similar pages is to use HOCs, which are just functions that return other functions.

HOCs will normally be put outside of pages/ and then required from multiple locations, but when you are about to factor things out to generalize, you might be tempted to put them directly in the pages/ file temporarily, something like:

// Client + server code.  import Link from 'next/link'  export function makeIndexPage(isIndex) {   return (props) => {     return <>       <Link href={isIndex ? '/index' : '/notindex'}>         <a>{isIndex ? 'index' : 'notindex'}</a>       </Link>       <div>{props.fs}</div>       <div>{props.isBlue}</div>     </>   } }  export default makeIndexPage(true)  // Server-only code.  const fs = require('fs')  export function makeGetStaticProps(isBlue) {   return () => {     return { props: {       fs: Object.keys(fs).join(' '),       isBlue,     } }   } }  export const getStaticProps = makeGetStaticProps(true) 

but if you do this you will be saddened to see:

Module not found: Can't resolve 'fs'  

So we understand another thing: the fs usage has to be directly inside the getStaticProps function body, Webpack can't catch it in subfunctions.

The only way to solve this is to have a separate file for the backend-only stuff as in:

pages/index.js

// Client + server code.  import { makeIndexPage } from "../front"  export default makeIndexPage(true)  // Server-only code.  import { makeGetStaticProps } from "../back"  export const getStaticProps = makeGetStaticProps(true) 

pages/notindex.js

// Client + server code.  import { makeIndexPage } from "../front"  export default makeIndexPage(false)  // Server-only code.  import { makeGetStaticProps } from "../back"  export const getStaticProps = makeGetStaticProps(false) 

front.js

// Client + server code.  import Link from 'next/link'  export function makeIndexPage(isIndex) {   return (props) => {     console.error('page');     return <>       <Link href={isIndex ? '/notindex' : '/'}>         <a>{isIndex ? 'notindex' : 'index'}</a>       </Link>       <div>{props.fs}</div>       <div>{props.isBlue}</div>     </>   } } 

back.js

// Server-only code.  const fs = require('fs')  export function makeGetStaticProps(isBlue) {   return () => {     return { props: {       fs: Object.keys(fs).join(' '),       isBlue,     } }   } } 

Webpack must see that name makeGetStaticProps getting assigned to getStaticProps, so it decides that the entire back file is server-only.

Note that it does not work if you try to merge back.js and front.js into a single file, probably because when you do export default makeIndexPage(true) webpack necessarily tries to pull the entire front.js file into the frontend, which includes the fs, so it fails.

This leads to a natural (and basically almost mandatory) split of library files between:

  • front.js and front/*: front-end + backend files. These are safe for the frontend. And the backend can do whatever the frontend can do (we are doing SSR right?) so those are also usable from the backend.

    Perhaps this is the idea behind the conventional "components" folder in many official examples. But that is a bad name, because that folder should not only contain components, but also library components that will be used from the frontend.

  • back.js and back/* (or alternatively anything outside of front/*): backend only files. These can only be used by the backend, importing them on frontend will lead to the error