Skip to content

Instantly share code, notes, and snippets.

@lili21
Forked from jacob-ebey/deferred-overview.md
Created February 25, 2023 16:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lili21/353e03cb2dd4d7d11a1f7398138e4395 to your computer and use it in GitHub Desktop.
Save lili21/353e03cb2dd4d7d11a1f7398138e4395 to your computer and use it in GitHub Desktop.
Deferred Overview

Remix Deferred

Remix Deferred is currently implemented on top of React's Suspense model but is not limited to React. This will be a quick dive into how "promise over the wire" is accomplished.

SSR + Hydration

It isn't rocket science, but a quick recap of how frameworks such as react do SSR:

  1. Load data
  2. Render the app
  3. Serialize the data
// 1. load data
const data = await loadData(request);
// 2. render app
const appHtml = toString(<App data={data} />);

response.end(
  `
    <!DOCTYPE>
    <html>
      <div id="app">
        ${appHtml}
      </div>
      <!-- 3. serialize data -->
      <script>window.__data = ${JSON.stringify(data)};</script>
      <script src="entry.client.js"></script>
    </html>
  `
);

Then on the client side, hydration occurs by reading the serialized data from the window:

const data = window.__data;
hydrate(document.getElementById("app"), <App data={data} />);

Simple yet effective.

How Deferred Differs

Remix deferred operates on the same general pattern as your standard SSR + Hydration setup, with the caveat of if a promise is returned as a data key, it "teleports" over the network and arrives in the browser also as a promise.

A high level of how this is accomplished can be understood by the following snippet:

  1. Load data
  2. Render the app
  3. Separate critical & deferred data
  4. Serialize critical data
  5. Teleport promises
  6. Transport promise resolutions
// 1. load data
const data = await loadData(request);
// 2. render app
const appHtml = toString(<App data={data} />);

// 3. separate critical & deferred data
const criticalData = {};
const deferredData = {};
const deferredSetupScripts = [
  // function used to setup deferred promises
  `
    window.__deferred = {};
    function setupDeferredPromise(key) {
      const promise = new Promise((resolve, reject) => {
        window.__deferred[key] = {
          resolve,
          reject,
        };
      });
      window.__data[key] = promise;
    }
  `,
  // function used to resolve / reject deferred promises
  `
    function resolveDeferredPromise(key, reason, result) {
      if (reason) {
        window.__deferred[key].reject(reason);
      } else {
        window.__deferred[key].resolve(result);
      }
    }
  `,
];
for (const [key, value] of Object.entries(data)) {
  if (value && value instanceof Promise) {
    deferredData[key] = value;

    deferredSetupScripts.push(`
      setupDeferredPromise(${JSON.stringify(key)});
    `);
  } else {
    criticalData[key] = value;
  }
}

response.send(
  `
    <!DOCTYPE>
    <html>
      <div id="app">
        ${appHtml}
      </div>
      <script>
        // 4. serialize critical data
        window.__data = ${JSON.stringify(criticalData)};

        // 5. teleport promises
        ${deferredSetupScripts.join("\n")}
      </script>
      <script src="entry.client.js"></script>
    </html>
  `
);

// 5. transport promise resolutions
await Promise.allSettled(
  Object.entries(deferredData).map(async ([key, promise]) => {
    try {
      const result = await promise;
      response.send(
        `
          <script>
            resolveDeferredPromise(
              ${JSON.stringify(key)},
              undefined,
              ${JSON.stringify(result)}
            );
          </script>
        `
      );
    } catch (reason) {
      response.send(
        `
          <script>
            const reason = new Error(${JSON.stringify(reason.message)});
            ${
              reason.stack
                ? `reason.stack = ${JSON.stringify(reason.stack)}`
                : ""
            }
            resolveDeferredPromise(
              ${JSON.stringify(key)},
              reason,
            );
          </script>
        `
      );
    }
  })
);

response.end();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment