v0.10.0 Release Notes

Release Overview on YouTube

Root Layout Route

Background

The App.tsx file has been a little bit goofy. This release cleans that up. Instead of needing to import the app file into both entry-server and entry-client, we've now got the concept of a "root route". This root route works like all others, it can export a loader and error boundary, etc.

This small change removes a few APIs and a lot of complexity in Remix's internals. It was one of those -200 line changes without losing any features, which always feels amazing.

Upgrading:

Move App.tsx to root.tsx

Move your App.tsx to root.tsx. "root" has special meaning, so it must be named that.

Change <Routes/> to <Outlet/>

Now that root.tsx is a route, you render an Outlet just like every other layout route.

// OLD
import { Routes } from "remix";

export default function App() {
  return (
    <html>
      {/* ... */}
      <body>
        <Routes />
      </body>
    </html>
  );
}

// NEW
// Note it's from "react-router-dom"!
import { Outlet } from "react-router-dom";

export default function App() {
  return (
    <html>
      {/* ... */}
      <body>
        <Outlet />
      </body>
    </html>
  );
}

Move global data code

Because root.tsx iss now a route it can export a loader for it's own data. If you had a global-data.ts file, cut and paste the code into root.tsx so it's co-located like all of your other routes.

Also, there is no longer a useGlobalData hook because this is now normal route data. So change your useGlobalData() to useRouteData().

Update your server and browser entries

Don't import or render App.tsx in entry-server or entry-browser anymore:

// OLD: entry-server
import App, { ErrorBoundary } from "./App";

let markup = ReactDOMServer.renderToString(
  <Remix context={remixContext} url={request.url} ErrorBoundary={ErrorBoundary}>
    <App />
  </Remix>
);

// NEW: entry-server
// - no more App import because you moved it to `root.tsx`
// - no more ErrorBoundary prop because the root route exports
//   it's own error boundary like any other route
let markup = ReactDOMServer.renderToString(
  <Remix context={remixContext} url={request.url} />
);

Do the same thing in entry-browser.tsx, it'll end up looking something like this:

import ReactDOM from "react-dom";
import Remix from "@remix-run/react/browser";

ReactDOM.hydrate(<Remix />, document);

Much nicer! No more special casing the root layout. We're also thinking this opens up the possibility for multiple root layouts: think signed-in-layout and signed-out-layout". We're not there yet though :)

Importing images

You can now import and resize images as a JavaScript module containing links to the assets, height and width attributes, and even responsive image source sets. Check it out!

// - change quality to 50
// - reformat to avif
// - generate a server rendered Base64 blurry placeholder
// - resize to 500
import guitar from "img:./guitar.jpg?quality=50&format=avif&placeholder&width=500";

// - change quality to 80
// - generate 3 responsive image sizes and a srcset for the `<img srcSet/>`
// - generate a ssr placeholder
import guitar2 from "img:./guitar.jpg?quality=80&srcset=720,1080,2048&placeholder";

export default function Guitar() {
  return (
    <div>
      <p>Fixed Image</p>
      <img
        alt="Guitar"
        src={guitar.src}
        style={{
          backgroundImage: `url(${guitar.placeholder})`,
          backgroundSize: "cover"
        }}
        width={guitar.width / 2}
        height={guitar.height / 2}
      />

      <p>Responsive</p>
      <img
        alt="Guitar"
        src={guitar2.src}
        srcSet={guitar2.srcset}
        style={{
          backgroundImage: `url(${guitar2.placeholder})`,
          backgroundSize: "cover"
        }}
      />
    </div>
  );
}

Go read the docs for more information!