2

We have a requirement to use Loadable Components in a new NextJS app we are building. I believe in NextJS for this use case you tend to use their native dynamic import feature but we are pulling in code from our mature codebase with extensive use of 'Loadable Components' throughout so replacement with dynamic imports is pretty impractical (PR is here in our main codebase: https://github.com/bbc/simorgh/pull/10305).

I have put together a representive example in a repo to demonstrate an issue we are having: https://github.com/andrewscfc/nextjs-loadable

In this example I have introduced a loadable component to split the Layout component into its own bundle:

import * as React from 'react';
import Link from 'next/link';
import loadable from '@loadable/component';

const LayoutLoadable = loadable(() => import('../components/Layout'));

const IndexPage = () => (
  <LayoutLoadable title="Home | Next.js + TypeScript Example">
    <h1>Hello Next.js </h1>
    <p>
      <Link href="/about">
        <a>About</a>
      </Link>
    </p>
  </LayoutLoadable>
);

export default IndexPage;

You can run this repo by running: yarn && yarn dev (or equivalent npm commands)

If you navigate to http://localhost:3000/ the page body looks like this:

<body>
    <div id="__next" data-reactroot=""></div>
    <script src="/_next/static/chunks/react-refresh.js?ts=1663916845500"></script>
    <script id="__NEXT_DATA__" type="application/json">
      {
        "props": { "pageProps": {} },
        "page": "/",
        "query": {},
        "buildId": "development",
        "nextExport": true,
        "autoExport": true,
        "isFallback": false,
        "scriptLoader": []
      }
    </script>
  </body>

Notice there is no html in the body other than the root div the clientside app is hydrated into: <div id="__next" data-reactroot=""></div>

The SSR is not working correctly but the app does hydrate and show in the browser so the clientside render works.

If you then change to a regular import:

import * as React from 'react';
import Link from 'next/link';
import Layout from '../components/Layout';

const IndexPage = () => (
  <Layout title="Home | Next.js + TypeScript Example">
    <h1>Hello Next.js </h1>
    <p>
      <Link href="/about">
        <a>About</a>
      </Link>
    </p>
  </Layout>
);

export default IndexPage;

The body SSRs correctly:

body>
    <div id="__next" data-reactroot="">
      <div>
        <header>
          <nav>
            <a href="/">Home</a>
            <!-- -->|<!-- -->
            <a href="/about">About</a>
            <!-- -->|<!-- -->
            <a href="/users">Users List</a>
            <!-- -->| <a href="/api/users">Users API</a>
          </nav>
        </header>
        <h1>Hello Next.js </h1>
        <p><a href="/about">About</a></p>
        <footer>
          <hr />
          <span>I&#x27;m here to stay (Footer)</span>
        </footer>
      </div>
    </div>
    <script src="/_next/static/chunks/react-refresh.js?ts=1663917155976"></script>
    <script id="__NEXT_DATA__" type="application/json">
      {
        "props": { "pageProps": {} },
        "page": "/",
        "query": {},
        "buildId": "development",
        "nextExport": true,
        "autoExport": true,
        "isFallback": false,
        "scriptLoader": []
      }
    </script>
  </body>

I have attempted to configure SSR as per Loadable Component's documentation in a custom _document file:

import Document, { Html, Head, Main, NextScript } from 'next/document';
import * as React from 'react';
import { ChunkExtractor } from '@loadable/server';
import path from 'path';

export default class AppDocument extends Document {
  render() {
    const statsFile = path.resolve('.next/loadable-stats.json');

    const chunkExtractor = new ChunkExtractor({
      statsFile,
    });

    return chunkExtractor.collectChunks(
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

This is not working correctly and imagine it maybe because there is no where to call renderToString(jsx) as per their docs; I think this call happens internally to NextJS.

Has anyone successfully configured loadable components in NextJS with SSR? I can't seem to find the right place to apply Loadable Component's SSR instructions?

andrew_scfc
  • 645
  • 3
  • 16
  • 1
    First of all, you are right that that's a huge code base. That's a lot of work. But IMO, finding what want you are looking for might be even harder than migrating loadable to dynamic because as you may have already noticed, no one is gonna use loadable components in Next.js. Since loadable also works in CSR, would it be better to forget about the SSR part at the moment? And when things get stable, migrate loadable to dynamic which the latter has SSR support with zero configs? – Matthew Kwong Sep 23 '22 at 07:45
  • Hey @MatthewKwong, thanks for your thoughts, they sound very sensible. A slight nuance we have is we are likely to keep our original app running for quite a while as gradually move to next; we want to share code between both apps as we transition. So ideally it would be good to have a solution the works both in Express and Next. I'm wondering if some kind of custom babel plugin could replace loadable calls with next's dynamic imports? Anyway hope someone can get loadable working that would be easier! – andrew_scfc Sep 28 '22 at 08:26
  • Try to use dynamic imports/modules from the nextjs documentation, i think is going to solve what are you struggling with. – Feisal Ali Sep 28 '22 at 11:32

0 Answers0