Upgrade your Remix app to React 18
Roger Stringer / July 5, 2022
3 min read
It is easy to quickly upgrade a Remix app to React 18.
In this example, I'll use a fresh Remix installation by running:
npx create-remix@latest test
Then go into the folder and run:
npm install react@latest react-dom@latest isbot
Followed by:
npm install @types/react@latest @types/react-dom@latest --save-dev
This will upgrade our libraries to use React 18.
Now open your app/entry.client.tsx
file:
import { RemixBrowser } from "@remix-run/react";
import { hydrate } from "react-dom";
hydrate(<RemixBrowser />, document);
And replace with it a new version:
import { hydrateRoot } from "react-dom/client";
import { RemixBrowser } from "@remix-run/react";
hydrateRoot(document, <RemixBrowser />);
Now let's do the same to our app/entry.server.tsx
file, to use the new renderToPipeableStream
API:
import type { EntryContext } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
let markup = renderToString(
<RemixServer context={remixContext} url={request.url} />
);
responseHeaders.set("Content-Type", "text/html");
return new Response("<!DOCTYPE html>" + markup, {
status: responseStatusCode,
headers: responseHeaders,
});
}
And replace with it a new version:
import { PassThrough } from "stream";
import { renderToPipeableStream } from "react-dom/server";
import { RemixServer } from "@remix-run/react";
import type { EntryContext } from "@remix-run/node";
import { Response, Headers } from "@remix-run/node";
import isbot from "isbot";
const ABORT_DELAY = 5000;
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const callbackName = isbot(request.headers.get("user-agent"))
? "onAllReady"
: "onShellReady";
return new Promise((resolve, reject) => {
let didError = false;
const { pipe, abort } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} />,
{
[callbackName]() {
let body = new PassThrough();
responseHeaders.set("Content-Type", "text/html");
responseHeaders.set("Transfer-Encoding", "chunked");
responseHeaders.set("Connection", "keep-alive");
resolve(
new Response(body, {
status: didError ? 500 : responseStatusCode,
headers: responseHeaders,
})
);
pipe(body);
},
onShellError(err) {
reject(err);
},
onError(error) {
didError = true;
console.error(error);
},
}
);
setTimeout(abort, ABORT_DELAY);
});
}
Your Remix app now runs React 18 successfully. I've used this process to upgrade all of my Remix apps so far without any issues.
One Last Thing...
I bet you’re wondering why we do the isbot
check right?
const callbackName = isbot(request.headers.get("user-agent"))
? "onAllReady"
: "onShellReady";
In React 18, onAllReady
is used for static generation such as with crawlers, and onShellReady
is called when all components are rendered before the suspense boundaries, if you decided to do all static then you can replace this with onAllReady
instead.