How to dynamically modify a JS library import to remove next.js server side rendering

I’m using rescript-request https://github.com/bloodyowl/rescript-request to issue XMLHttp requests in my Vercel site. Unfortunately, Vercel attempts to aggressively use server side rendering and XMLHttp has no implementation in node.js.

As a result, I get the following error:

Error occurred prerendering page "mypage". Read more: https://nextjs.org/docs/messages/prerender-error
ReferenceError: XMLHttpRequest is not defined

There is normally a simple fix for this, just import the module with SSR disabled, like so: Advanced Features: Dynamic Import | Next.js. Even better, I’m using the rescript-nextjs-template and it does seem to have support for dynamic: https://github.com/ryyppy/rescript-nextjs-template/blob/260c38abd095430a3b15e18ac2908e1ef61953c9/src/bindings/Next.res#L141.

But how do I put this together?

In particular, I don’t seem to have control over where the underlying rescript-request module gets imported and therefore am not sure how to annotate that import with dynamic.

Update: I’ve also posed this question inline to library authors: https://github.com/ryyppy/rescript-nextjs-template/issues/70.

Are you thinking of dynamically loading your page component ("mypage")? I don’t get the rationale between XMLHttpReqeust is not defined and Advanced Features: Dynamic Import | Next.js.

1 Like

I believe that the XMLHttpRequest is not defined error comes from the attempted pre-rendering of rescript-request requests (XMLHttpRequest is not available in node). This error only occurs if I refresh the page, but doesn’t occur if I switch to a page that wants to issue XMLHttpRequest.

The way to shut down SSR rendering in next.js is using dynamic imports for relevant libraries (in this case rescript-request) to make sure that they are not used server side.

To answer your question, I guess I don’t need the whole component to be dynamically loaded (some of it may be pre-rendered successfully) as long as anything that uses rescript-request is dynamically loaded.

Way #1

Make a simple helper to check if you’re in the SSR environment:

let isSSR = %raw(`typeof window == 'undefined'`)

Then use it to avoid browser-specific stuff on the server. Here’s an example. Not about HTTP request, but local storage, nevertheless:

let useSessionState = (defaultValue, key) => {
  let loadValue = () =>
    if Next.isSSR {  // <- Branch here as there’s no Dom.Storage.sessionStorage on server
      defaultValue
    } else {
      let maybeStoredValue = Dom.Storage.sessionStorage->Dom.Storage.getItem(key, _)
      switch maybeStoredValue {
      | Some(value) => value
      | None => defaultValue
      }
    }

  let (value, rawUpdateValue) = React.useState(loadValue)

  let updateValue = fn => {
    let newValue = fn(value)
    Dom.Storage.sessionStorage->Dom.Storage.setItem(key, newValue, _)
    rawUpdateValue(_ => newValue)
  }

  (value, updateValue)
}

Way #2

Depends on a type of the page, but in some cases it makes sense to forbid SSR for the whole page completely:

// packages/cut-app/pages/checkout.js

import dynamic from "next/dynamic";

const WorkshopPageRes = dynamic(
  async () => (await import("../src/client/Workshop/WorkshopPage.bs.js")).make,
  { ssr: false } // <- This option
);

export default function WorkshopPage({ ...props }) {
  return <WorkshopPageRes mode="checkout" {...props} />;
}
2 Likes

Thank you very much! The second example didn’t work (component doesn’t end up being rendered) but the first one worked well.

Not sure why the second one does not work for you. Perhaps, some slight nuances or differences in next.js versions? I just copy-pasted it from the code used in production. Requires some debugging.

And here’s a bonus snippet. The same as above, but shows a progress bar so that the page loading process has better UX:

import dynamic from "next/dynamic";
import LinearProgress from "@material-ui/core/LinearProgress";

const WorkshopPageRes = dynamic(
  async () => (await import("../src/client/Workshop/WorkshopPage.bs.js")).make,
  {
    loading: () => <LinearProgress color="secondary" />,
    ssr: false,
  }
);

export default function WorkshopPage({ ...props }) {
  return <WorkshopPageRes mode="project" {...props} />;
}
1 Like