Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exclude a module client side #2069

Closed
sonnyp opened this issue May 25, 2017 · 23 comments
Closed

exclude a module client side #2069

sonnyp opened this issue May 25, 2017 · 23 comments
Assignees

Comments

@sonnyp
Copy link

sonnyp commented May 25, 2017

Hello,

I'd like to exclude a specific module that uses fs from the client side bundle. I've tried

  "browser": {
    "./lib/api.js": false
  }

in my package.json but I get Error: Cannot find module '../lib/api' so it seems to be excluded server side as well somehow.

My use case is that I want my getInitialProps function to use fetch client side and an other method from my ./lib/api.js file server side.

@sonnyp sonnyp changed the title ignore a module client side exclude a module client side May 25, 2017
@arunoda
Copy link
Contributor

arunoda commented May 26, 2017

EDIT: Following approach works but it's incorrect. Even though the module is loaded only in the server, it's bundled by webpack anyway. So, that's not a good solution.

In this case, the best way to use dynamic imports which comes with v3. See: https://zeit.co/blog/next3-preview

You can do something like this:

Comp.getInitialProps = async (ctx) => {
  if (isServer) {
    const api = await import('./lib/server-api.js')
    // do something
  } else {
    const api = await import('./lib/client-api.js')
    // do something
  }
}

@sonnyp
Copy link
Author

sonnyp commented May 26, 2017

Thanks.

It does sounds like a workaround though. Dynamic imports is a great feature but I don't feel they fit my use case, it's not like I need them on demand and I'd rather have my imports at the top of my file.

Does it means next.js is incompatible with libraries which depends on the webpack/browserify browser field of package.json?

@sonnyp
Copy link
Author

sonnyp commented Jun 1, 2017

related #1091

I just had a look at isomorphic-fetch package.json file

{
  "browser": "fetch-npm-browserify.js",
  "main": "fetch-npm-node.js",
}

so Next.js does honor the browser field for external packages (via webpack I guess). It apparently doesn't for the application own package.json (at least for paths).

Let me know if you think this is in scope for Next.js and I'll try to send a PR.

Cheers

@fatfisz
Copy link
Contributor

fatfisz commented Jun 3, 2017

@arunoda FYI this returns errors:

if (false) {
  await import('request');
}
These modules were not found:

* fs in ./~/request/lib/har.js
* net in ./~/forever-agent/index.js, ./~/tunnel-agent/index.js and 1 other
* tls in ./~/forever-agent/index.js, ./~/tunnel-agent/index.js

To install them, you can run: npm install --save fs net tls

So branching in the code is not really possible.

@arunoda
Copy link
Contributor

arunoda commented Jun 13, 2017

@fatfisz this a good one.
I think even if it's not downloaded at once. Webpack is looking to bundle it.
I hope we need to do something about this.

@arunoda arunoda self-assigned this Jun 13, 2017
@arunoda
Copy link
Contributor

arunoda commented Jun 16, 2017

There are couple of ways we can fix this. Here's the easy way:

Comp.getInitialProps = async (ctx) => {
  if (isServer) {
    const api = eval(`require('./lib/server-api.js')`)
    // do something
  } else {
    const api = await import('./lib/client-api.js')
    // do something
  }
}

@arunoda
Copy link
Contributor

arunoda commented Jun 29, 2017

I wrote a blog post about this here - https://arunoda.me/blog/ssr-and-server-only-modules

Frankly, there's nothing we can do about this in Next.js other than educating everyone.

@arunoda arunoda closed this as completed Jun 29, 2017
@haohcraft
Copy link

haohcraft commented Feb 7, 2018

Have you used this webpack-node-externals to keep the webpack from bundling the specific libs only used on server? https://github.com/liady/webpack-node-externals

Using eval is hacky to webpack.

@timneutkens
Copy link
Member

@haohcraft you can use solutions like that now, but I wouldn't recommend adding webpack-node-externals 👍

@yamsellem
Copy link

yamsellem commented Sep 9, 2018

@arunoda thanks for your article on the matter.

Using the same technique, I was forced to change the require statement like this:

const repository = eval("require('../../../../repositories/place-repository')");

Even if folders repositories and pages are in the same lib folder.
But 'next' don't seems to look that way.

The error is:

Cannot find module '../repositories/place-repository'
    at ...
    at /Users/macbook/code/project/lib/.next/server/bundles/pages/places.js:1383:26

Like, instead of looking in the current server folder, lib, the eval took place in the generated next folder.

Is there a way to use a usual require path in those kind of situations?
Or is this a commun workaround?

Thanks.

@jpedroribeiro
Copy link

@timneutkens is eval still the recommended approach for this situation?

@codyzu
Copy link

codyzu commented Jan 20, 2019

I found an interesting solution:

  1. serve next using an express server as shown here
  2. require whatever server side modules you need inside your server.js and add them the req via an express middleware
const myModule = require('this-only-works-on-the-server');

// ... initialize express + next

server.use((req, res, next) => {
  // now I can access this module inside the getInitialProps function
  req.myModule = myModule;
  return next();
})

server.get('*', (req, res) => {
  return handle(req, res);
})

☝️ this avoids webpack ever trying to include the server side module since the server.js file is never imported by webpack.

@knitkode
Copy link

hi @codyzu have you found a way to make this work also on next export?

@codyzu
Copy link

codyzu commented Jan 21, 2019

hello @knitkoder ... no, I haven't used the export functionality. However, my feeling is it would be strange impossible to use server side imports/requires in a exported static site. By definition, a static site doesn't have any server side JS running.

@knitkode
Copy link

knitkode commented Feb 11, 2019

thanks @codyzu I ended up using gatsby to combine data sources from js files, in the end that was what I needed

@ayessymkanov
Copy link

@codyzu you made my day!!))

@jesstelford
Copy link
Contributor

jesstelford commented Apr 4, 2019

Using process.browser (from webpack) is the most effective way to deal with this:

let getData;
if (process.browser) {
  getData = () => fetch('https://....');
} else {
  const api = require('lib/api');
  getData = () => api.foo();
}

export default class extends React.Component {
  static async getInitialProps() {
    const { data } = await getData();
    return {
      data,
    };
  }
  // ...
}

The browser-side code then compiles down to something like:

var getData;

if (false) { var api; } else {
  getData = function getData() {
    return fetch('https://...');
  };
}

// ...

For a real-world example of this pattern, see: https://codesandbox.io/s/n3yj15mk7p

@nfantone
Copy link

nfantone commented Jan 24, 2020

@arunoda Is the guide on SSR and Server Only Modules still relevant after almost three years?

I've been trying to follow instructions there but I'm unsure how they should actually work.

If I ignore a module (using IgnorePlugin) that's meant to be loaded server side, I -unsurprisingly- get a MODULE_NOT_FOUND error on the server console.

Error: Cannot find module 'jsonwebtoken'
    at webpackMissingModule (/my-project/.next/server/static/development/pages/index.js:1891:79)
# ...
  code: 'MODULE_NOT_FOUND'
}

I'm trying to validate a JWT token on the server with jsonwebtoken, BTW. Below is my current setup.

// next.config.js

module.exports = {
  // Do not show the X-Powered-By header in the responses
  poweredByHeader: false,
  webpack: (config, { webpack }) => {
    // Avoid including jsonwebtoken module in the client side bundle
    // See https://arunoda.me/blog/ssr-and-server-only-modules
    config.plugins.push(new webpack.IgnorePlugin(/jsonwebtoken/));

    return config;
  }
};
// auth-token.js (server side only module)

export async function verifyAuthToken(token) {
  // Import `jsonwebtoken` here using `require` as this
  // is meant to be a server-side only module
  // See https://arunoda.me/blog/ssr-and-server-only-modules
  const jwt = require('jsonwebtoken');

  return new Promise((resolve, reject) => {
    jwt.verify(token, JWT_SECRET, (err, decoded) => {
      return err ? reject(err) : resolve(decoded);
    });
  });
}
// private-page.js

// ...

  PrivatePage.getInitialProps = async ctx => {
    // Extract authorization token from cookies
    const token = getAuthToken(ctx);

    if (ctx.req) {
	  // Server only
      try {
        const auth = await verifyAuthToken(token);
        return { auth };
      } catch (err) {
        console.log(err); // <-- this prints the MODULE_NOT_FOUND error server side
 
        // User is either not authenticated or the token verification
        // failed: we return `false` as the value for the `auth` prop;
        // this should trigger a redirect to the login page
        return { auth: false };
      }
    }
	
    // Client only
    return { auth: false };
  };

What am I missing?

EDIT: Found the solution. I tripped over the fact that I'm excluding the module for both client and server compilations. The isServer property, as documented here, worked for this.

const nextConfig = {
  // Do not show the X-Powered-By header in the responses
  poweredByHeader: false,
  webpack: (config, { webpack, isServer }) => {
    // Avoid including jsonwebtoken module in the client side bundle
    // See https://arunoda.me/blog/ssr-and-server-only-modules
    if (!isServer) {
      config.plugins.push(new webpack.IgnorePlugin(/jsonwebtoken/));
    }

    return config;
  }
};

@arunoda's 2017 blog post should probably be updated to reflect this - specially since it's linked directly on the official Next.js docs.

@flybayer
Copy link
Contributor

flybayer commented Mar 3, 2020

@nfantone's solution of using IgnorePlugin does not work on current Next.js canary.

So I've solved this using the following:

  1. yarn install null-loader
  2. Add the following to next.config.js
module.exports = {
  webpack(config, { isServer }) {
    if (!isServer) {
      // Ensure prisma client is not included in the client bundle
      config.module.rules.push({
        test: /@prisma\/client/,
        use: "null-loader",
      })
    }
    return config
  },
}

@florianmatz
Copy link

florianmatz commented Sep 16, 2020

@flybayer

Do you - because you've obviously had a working configuration - possibly know, where my mistake could be:

const withTM = require('next-transpile-modules')(['nd-component-factory']);
module.exports = withTM({
    async redirects() {
        return [
             {
                source: '/',
                destination: '/projekte',
                permanent: true,
            },
            {
                source: '/projekte/:id',
                destination: '/projekte',
                permanent: true,
            },
        ];
    },
    webpack(config, { isServer, webpack }) {
        if (isServer) {
            config.module.rules.unshift({
                test: /@azure\/msal-browser/,
                use: 'null-loader',
            });
        }
        return config;
    },
});

I have to exclude @azure/msal-browser from being imported in the server render, because it uses the window object which causes everything to crash.

I also tried requiring the null-loader package manual and put it to the config. Same result, not working.

@azure/msal-browser still gets included on the server side. The packaged is imported in a wrapper for this library. I need to export a hook from there, so I can't use dynamic() because it only generates LoadableComponents...

Help would be highly appreciated!

@flybayer
Copy link
Contributor

@florianmatz I think for that you definitely need to use await import('dep') or dynamic(() => import('dep')).

Probably you need to use dynamic() on the parent component of where the azure/msal hook is used.

@florianmatz
Copy link

@flybayer Yes, I have that, getting

const MsalConsumer = dynamic(() => import('auth').then(mod => mod.MsalConsumer), {
    ssr: false,
});

and

const MsalProvider = dynamic(() => import('auth').then(mod => mod.MsalProvider), { ssr: false });

For the Provider, everything is fine. But with the Consumer it's difficult, because - of some reasons, that are hard to describe here - I actually need to use a useContext Hook, not the Consumer.

So out of my auth-Module I need to import

export const useMsal = (): any => useContext(MsalContext);

But then again, the SSR breaks due to the @azure-package...

So I basically would need something like

const msalHook = dynamicFUNCTION(() => import('auth').then(mod => mod.useMsal), {
    ssr: false,
});

But of course, I only get the LoadableComponent...

The consumer or the hook are basically provding tokens that I need to call some APIs.

I need either to fix next ignoring the package on SSR (prefered) or Microsoft to fix their libraries to support SSR, which is unlikely :(

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests