Migrating Remix web applications to Node 20 and React Router v7

As Upgrading from Remix to React Router states “React Router v7 is the next major version of Remix after v2”. React Router v7 now contains the functionality of @remix-run/node, @remix-run/cloudflare, etc.

To upgrade from Remix to React Router 7

Upgrade Node to version 20

React Router v7 requires Node.js version 20 or higher, so at the time of writing most users have to upgrade Node.js.

If you used Homebrew to install Node.js, execute the commands

brew update       # Update Homebrew
brew tap --repair # If brew notifies you about broken references
brew upgrade node # Update Node.js to the latest stable version

Create a new Remix site using the React Router framework

Follow the instructions at React Router Installation.

At the time of writing these are the recommended steps:

Create the base application

npx create-react-router@latest my-react-router-app
  • The process will create a folder matching the name of the application you specify, and will add that name to the package.json file as the “name” element.
  • The .git directory, and the .gitignore file will be placed in the application directory. As an application repository usually contains more than just the web application, like Terraform scripts, documentation, move the .gitignore file higher, remove the .git directory and recreate the repository with the git init command in the top level directory of your application.
  • Update the .gitignore file, and remove the leading slashes from all lines, as the node_modules, .react-router, and build directories are not in the root directory anymore.
.DS_Store
node_modules/

# React Router
.react-router/
build/

Install the Node.js packages

cd my-react-router-app
npm i

Run the application

npm run dev

Navigate to http://localhost:5173 to view it in the browser.

Copy and update the source code

  • Copy the app/models, app/routes/, app/styles directories into the app folder of the new base application structure.
  • Execute the codemod to update the package references
    npx codemod remix/2/react-router/upgrade

Make the application work

Separate files for server side and client side functions

To make the application work using the new framework and the Vite development tools, we have to make sure the client side files do not reference any modules that are not available in the browser. These include imports of our functions with database access.

The Remix compiler in the past could reference the server side and client side functions from common helper files. Vite needs separate files for the server side and client side.

If a client side file imports a function that contains references to functionality that is not available in the browser, like file system or database access the application compiles without error, the server starts without error, but we will find error messages in the console window of the browser.

Module “events” has been externalized for browser compatibility. Cannot access “events.EventEmitter” in client code. See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.

Uncaught ReferenceError: process is not defined
at node_modules/pg/lib/defaults.js

In this example our client side helper file only imported, but not called a function that accessed the PostgreSQL database.

Another example, when a function (logInfo) is imported from the server side file, but not used.

Solution: Delete the unused references

To import only a URL

RollupError]: “default” is not exported by ‘./index.css’ imported by “app/index.tsx”.

Style sheets are not compiled into the code, only the browser needs access to them to render the page. To reference style sheets, we only need to import the URL, so the compiler can make sure those are available and include them in the build. To import only the URL, add ?url to the end of the style sheet path.

import { LinksFunction } from "@remix-run/node";

-import styles from "./index.css";
+import styles from "./index.css?url";

Render UI only in the browser

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
or
React.jsx: type is invalid — expected a string (for built-in components) or a class/function (for composite components) but got: object.

Vite tries to render client side elements in the server. In the client side code render client side code only in the browser.

  // Check if the code is running in a browser
  const [isClient, setIsClient] = React.useState(false);
  React.useEffect(() => {
    setIsClient(true);
  }, []);
  
  # Render the UI
  return (
    <>
      {isClient ? (
        <Header/>
      ):null}

      {isClient ? (
        <div>
        ... The rest of the UI
        </div>
      ):null}

      {/* Display the Footer component */}
      {isClient ? (
        <Footer/>
      ):null}
      </>

  );

Custom image in the expandIcon

During the migration the compiler did not accept the Material UI icon library. You can use a custom PGN image for the expandIcon of the AccordionSummary:

<AccordionSummary
  expandIcon={<img src="/expand-more-down.png" />}
...

Leave a comment

Your email address will not be published. Required fields are marked *