This is another major change (like 0.8) that is focused on improving the developer experience in Remix. It's a major change because it essentially changes one of our core assumptions about how people would be using Remix based on feedback we've received since we launched our supporter preview in October.
tl;dr: In version 0.9, the data
directory is gone (as is the dataDirectory
export in remix.config.js
). Instead, put your loader
and action
functions right there in your route modules (in app/routes
) next to your component, headers
, and meta
. Remix will automatically compile builds for both the server (for server rendering) and the browser. data/global.js
is now app/global-data.js
.
One of our main goals with Remix is that it doesn't have to own your entire stack. Sure, you could build an entire app on Remix. But if you have an existing node server, you don't have to abandon it or port everything to a new codebase when you decided to adopt Remix for your frontend. In line with this goal, Remix provides several different packages for working with various cloud providers including Architect (AWS Cloud Formation) and Vercel, and we are hard at work on many more.
We also assumed that, since Remix fits into your existing stack, we wouldn't have to handle compiling any of your backend code since you'd probably already have a build process in place. So we provided a data
directory for all backend code. While it's technically possible to compile the backend code yourself, what this means in practice is that in order to use Remix you have to set up a separate build for data
. And you probably don't already have a data
directory because before Remix came along you didn't structure your code like that.
Additionally, many people are using TypeScript these days (we are!) and it's inconvenient to have separate folders for your data loaders and components when they use the same types! This caused a few of you to create a root-level types
directory just so you could share code between data
and app
.
So, the assumption was that we didn't need to handle anything in the data
directory, but based on your feedback we can see clearly this needs to change!
As was mentioned previously, the data
directory is gone in 0.9.Instead of putting your loader
and action
functions in data/routes
, move those functions into the same corresponding files in app/routes
alongside your route components, headers
, and meta
functions. If you had a data/global.js
file, move it to app/global-data.js
. Then go delete your data
directory and your dataDirectory
export in remix.config.js
.
When remix run
or remix build
runs, Remix will generate two versions of your modules: one for the server (for server rendering) and one for the browser. For the browser build, Remix will automatically strip out any code that isn't meant to run in the browser. This means that server-only code in your loader
and action
functions (and any import
s that are used only by them) won't appear anywhere in the browser bundles.
So if you had this in data/routes/team.ts
:
import type { Loader } from "@remix-run/data";
import { db } from "../db";
export let loader: Loader = async () => {
return await db.query("select * from team");
};
Go ahead and move that code into app/routes/team.tsx
:
import { useRouteData } from "remix";
import type { Loader } from "@remix-run/data";
import { db } from "../db";
export let loader: Loader = async () => {
return await db.query("select * from team");
};
interface TeamMember {
name: string;
}
type Team = TeamMember[];
export default function MeetTheTeam() {
let team = useRouteData<Team>();
return (
<div>
<h1>Meet the Team</h1>
<ul>
{team.map(member => (
<li>{member.name}</li>
))}
</ul>
</div>
);
}
Now everything you need for a single route is in one spot; the data and the view. And don't forget you can always associate custom headers
and/or meta
information with a route in this file as well.
We've been using this already on our own projects internally and it feels really great to have everything in one spot. It's difficult to overstate the importance of avoiding context switching when working with code in order to move quickly and feel productive. One of the core innovations of React was keeping the state right there in the view, which made it feel incredibly productive almost immediately. We feel like this is a similar advantage of having everything in the same file in a route.
You might also feel like this makes it a little more tricky to think about what code in this file is going to run on the server and what code is going to run in the browser. But this isn't something new. Your components have always run on both the server and in the browser. That's just one of the trade-offs of server rendering! We are hoping that it will be easy enough to just remember that anything in loader
and action
won't make it to the browser.
You might be wondering how this all works behind the scenes, since any import
s of server-only libraries like @prisma/client
or firebase-admin
aren't ever supposed to run in the browser.
To build this feature, we relied heavily on Rollup's built-in tree-shaking capabilities. When we run the browser build, we tell Rollup to ignore the loader
and action
methods in the output. We also tell it to ignore any module-level side effects, like database client initialization logic, so it aggressively prunes out the imports of any code in the module graph that is used only in loader
and/or action
.
With the data directory, Remix wasn't compiling your TypeScript. This led to "two builds" in Remix. The application, not Remix, had to build TypeScript for the modules in your data directory, then Remix built TypeScript in your app. This made sharing code overly complicated and was just not as nice as having one build to worry about.
Because loaders/actions are inlined with your route modules, you no longer need a separate TypeScript build for data modules. If you used one of our starters, you can:
tsc -b
and tsc -w
code, you can remove ittsconfig.json
files except app/tsconfig.js
.In the Overview Video, you can see all of the places affected by this. We're really happy with this change because it simplifies a lot of things for your application and for Remix itself.
We are also including first-class support for error boundaries in this release. Of course, you've always been able to use React 16's built-in error boundaries in Remix, but we have taken it one step further and introduced a top-level ErrorBoundary
export in your route modules.
An ErrorBoundary
is a component that is rendered in place of your route component whenever there is an error on that route, whether it be a render error (from inside one of your route components) or an error thrown from inside one of your loaders or actions.
In addition to supporting error boundaries at the route level, we also include support for a global ErrorBoundary
as a prop to your <Remix>
element. All of our starter repos have been updated to show how this is to be done (see app/entry-browser.tsx
and app/entry-server.tsx
).
Instead of using app/routes/500.tsx
for your global error handler, Remix will now use your global ErrorBoundary
component. It will still automatically change the HTTP response status code to 500. Since this functionality is only for real errors (uncaught exceptions), 500 is the appropriate status code.
data/global.ts
to app/global-data.ts
dataDirectory
config in remix.config.js
data/routes/<data-module>.ts
to its corresponding app/routes/<route-module>.tsx
file.app/routes/500.tsx
fileErrorBoundary
components to your routes and/or your top-level <Remix>
elementapp/tsconfig.js