Stephen Isienyi
Stephen Isienyi

Reputation: 1352

Can't resolve child_process when using Nodemailer in Next.js

Using nodemailer in one of the pages/api routes produces the following error message:

[ error ] ./node_modules/nodemailer/lib/sendmail-transport/index.js Module not found: Can't resolve 'child_process' in 'C:\ua-demo\node_modules\nodemailer\lib\sendmail-transport'

With my understanding being that the next.js pages/api routes run in the server environment alone, it is a wonder that this error is occurring. How could this be addressed so that I can send email updates to my users?

An example has been added here at codesandbox.io. I think we need to build a copy of the program directly on a local machine to reproduce.

Nextjs issue discussion

Upvotes: 2

Views: 9824

Answers (3)

Mohit kumar
Mohit kumar

Reputation: 487

In my case, I was getting error because I was having a file in pages folder which was not exporting any React component and was exporting one util function.

Upvotes: 0

Stephen Isienyi
Stephen Isienyi

Reputation: 1352

I have discovered the source. When using Next.js, all modules including NPM packages resolving native server resources during build time need to be imported in server-side only modules. This is not as straight-forward as it sounds in a universal web application.

Doing something like the following contrived example in a universally used module will result in an error like this one: Can't resolve 'child_process' in 'C:\ua-demo\node_modules\nodemailer\lib\sendmail-transport' because child_process is a native server resource.

// send-mail/server.js
import nodeMailer from 'nodemailer';
import config from './some/nodemailer/config;

const transport = nodeMailer.createTransport( config );
const sendMail = message => transport.sendMail( message );

export default sendMail;

// send-mail/browser.js
import { post } from 'axios';

const sendMail = async ( axiosRequestConfig ) => {
    try {
        await post( axiosRequestConfig );
    } catch( e ) {
        console.error( e );
    }
};
export default sendMail;

// send-mail/index.js
import isBrowser from './some/browser/detection/logic';
import browserMailer from './browser';
import serverMailer from './server';

const mailer = isBrowser() ? browserMailer : serverMailer;

export default mailer;

Import this 'send-mail' module to your component with the belief that the browser check ensures the appropriate send-email logic at runtime. However, the build fails with a similar error to the one above. The solution here is to modify the send-mail module to defer its imports until runtime.

// send-mail/index.js
import dynamic from 'next/dynamic'; // Can also use other lazy-loading module mechanism here. Since we are building a next.js app here, why not use the one created specifically for next apps?
import isBrowser from './some/browser/detection/logic';

const mailer = isBrowser()
    ? dynamic(() => import( './server' ))
    : dynamic(() => import( './browser' ));

export default mailer;

If using webpack, we can set RUN_TARGET=BROWSER environment variable for the client-side builds and use webpack-conditional-loader to branch the code at build time instead of the dynamic runtime loading as in:

// #if process.env.RUN_TARGET !== 'BROWSER'
import serverMailer from './server';
// #endif
// #if process.env.RUN_TARGET === 'BROWSER'
import browserMailer from './browser';
// #endif
let mailer;
// #if process.env.RUN_TARGET !== 'BROWSER'
mailer = serverMailer;
// #endif
// #if process.env.RUN_TARGET === 'BROWSER'
mailer = browserMailer;
// #endif

export default mailer;

// yeilds the following after server-side build
import serverMailer from './server';
let mailer;
mailer = serverMailer;
export default mailer;

// yeilds the following after client-side build
import browserMailer from './browser';
let mailer;
mailer = browserMailer;
export default mailer;

One can also choose to remove the index.js branching, and manually import server email logic in server-side only modules and browser email logic in browser only modules. In a large application, this can become quite cumbersome if not impossible to handle. It is inadvisable to do this manually.

Upvotes: 4

Kim T
Kim T

Reputation: 6436

In my case I was importing a simple method which seemingly had only client-side code:

import { clientSideMethod } from 'mypackage'

clientSideMethod() lives in :

./node_modules/mypackage/dist/utils.js

But the error came from a completely different file:

Module not found: Can't resolve 'child_process'
error - ./node_modules/mypackage/dist/file.js:8:0

It seems that using a generic package import, traverses all files underneath regardless of whether the methods are actually called:

import { clientSideMethod } from 'mypackage'

Actually first goes to the root of the package and calls:

export * from './file'; // contains server-side code
export * from './utils'; // contains client-side code only

Therefore the error was triggered. Solutions, some are already mentioned above are:

  1. Use a more specific import import { clientSideMethod } from '../../node_modules/mypackage/dist/utils'
  2. Dynamically import server-side modules when needed
  3. Split packages or modules into client-side vs server-side

Very simple to fix, but frustrating to find the cause!

Upvotes: 1

Related Questions