How to Isolate Next.js Server-Only Modules
(source)
Introduction
- When performing a server-side render of a next.js React page component, some server-only code may be required (eg. fetch data from a in-memory server cache, server logging)
- However, some server-only code:
- Should not be executed on the client
- eg. bundling an in-memory XHR cache that was only meant to be used on the server-side
- Cannot be executed on the client
- eg. emitting server logs via
hot-shots
, which requires adgram
dependency that does not exist on the client bundle.
- eg. emitting server logs via
- Should not be executed on the client
- Thus, isolating next.js server-only modules helps to ensure that we avoid bundling server-only code into our client bundles.
- There are two general 2 approaches to do this:
- Isolate via calling server-only code within
getServerSideProps()
- Isolate via module import alias
- Isolate via calling server-only code within
Scenario
Given the following setup:
- A page is hosted on a next.js instance
- Page needs to make 1x XHR call as part of its server-side rendering step
- XHR call may be cached by using a
memcached
instance
Goal: We want to ensure that all memcached
-related code is not included in the next.js client bundle.
Note: Refer to my example repo nextjs-ssr-localcache-example for code and documentation references.
Approach 1: Isolate via calling server-only code within getServerSideProps()
This is the recommended way to isolate server-only modules.
- Given that the server-only code is called only within
getServerSideProps()
, - When the client bundle build step is performed,
- Then next.js will perform tree-shaking for
getServerSideProps()
, thus ensuring that server-only code will neither be bundled nor invoked on the client side.
Note: This also works for getStaticProps()
as well.
Steps:
-
Invoke server-only code in
getServerSideProps()
// UserCachedSspPage.tsx const User = (props) => { ... }; export async function getServerSideProps() { const xhrCacheHelper = makeXhrCacheHelper(); if (xhrCacheHelper) { const { getKey } = xhrCacheHelper; const dataString = (await getKey("userData")) as string; ... } ... }
Approach 2: Isolate via module import alias
However, if you’re in either of these scenarios:
- You are on a version of next.js
<9.3
, wheregetServerSideProps()
is not available- eg. executing server-only code within
getInitialProps()
- eg. executing server-only code within
- You want to execute some server-side code within React components that are invoked during a server-side page render
- eg. Collecting performance metrics and emitting logs on server-side only
…then you would need to manually isolate server-only code.
Steps:
-
Organize server-only code in a separate folder (eg.
src/SERVER_ONY_MODULES
)// src/SERVER_ONLY_MODULES/xhrCacheHelper/xhrCacheHelper.ts import Memcached from "memcached"; export const makeXhrCacheHelper = () => { const memcached = new Memcached("..."); const closeConnection = () => { ... }; const setKey = (key, value) => { ... }; const getKey = (key) => { ... }; return { setKey, getKey, closeConnection }; };
xhrCacheHelper()
defines an object with functions to get/set values with thememcached
instance. -
Access server-only code using import aliases (eg.
@SERVER_ONLY_MODULES
)// UserCachedIpPage.tsx const getMakeXhrCacheHelper = async () => { const module = await import("@SERVER_ONLY_MODULES"); if (!module) { return noop; } return module.makeXhrCacheHelper; }; const User = (props) => { ... }; User.getInitialProps = async () => { const makeXhrCacheHelper = await getMakeXhrCacheHelper(); ... }
getMakeXhrCacheHelper()
is a utility that defensively checks for the existence of the module; this is necessary as theimport()
statement would not resolve to the actual module (ie. returnundefined
) when executed on the client browser. -
Isolate server-only module imports for client bundle builds (via webpack config override via next.config.js)
// next.config.js const webpackConfigFn = (config, { isServer }) => { // for webpack to resolve import alias correctly config.resolve.alias["@SERVER_ONLY_MODULES"] = "src/SERVER_ONLY_MODULES"; if (!isServer) { // resolve @SERVER_ONLY_MODULES as empty module on client config.resolve.alias["@SERVER_ONLY_MODULES"] = false; } return config; };
We provide configuration to support the
@SERVER_ONLY_MODULES
import alias, as well as to resolve@SERVER_ONLY_MODULES
to an empty module on the client browser.
Appendix
This article is inspired by Arunoda Susiripala’s article, SSR and Server Only Modules (referenced by official next.js documentation).
However, I found that I was still getting bundle build errors and client bundle runtime errors - This article fleshes out an alternative approach to isolating server-only modules.
Likewise as with Arunoda’s article, I hope this article improves the developer community’s awareness of this issue 🙃