Skip to content

Releases: remix-run/remix

v0.19.1

09 Oct 15:05
Compare
Choose a tag to compare
  • Type fixes
    • EntryContext
    • Route
    • LinksFunction
  • Bugfixes
    • Routes without loaders were being called on client side transitions

v0.19.0

08 Oct 18:40
Compare
Choose a tag to compare

published: 2021-10-07

v0.19.0 Release Notes

Holy smokes this is a big release with tons of good stuff. Some let you handle new use-cases, some clean up your code, and others automatically make your website better and you don't have to do anything. This release puts us within inches of a stable v1.

The biggest piece of work in this release is the rewrite of client side transitions. This enabled us to add a handful of new features, fix some bugs, and make it more efficient for the browser and faster for user at the same time.

When the URL changes Remix does a bunch of communication with the server. We used to have a 300 line useEffect that just kind of did everything. We lovingly referred to it as "the big effect". We knew it was incomplete, but we were waiting to see how the rest of Remix shook out before really tackling this work. The time came and we spent months getting it right. Most of the features in this release are from that work or built on top of it.

tl;dr Upgrade Guide and Breaking Changes

  • Upgrade to [email protected]
  • useRouteData -> useLoaderData
  • usePendingFormSubmit -> useTransition().submission
  • usePendingLocation -> useTransition().location
  • block({ rel: "preload", as: "image", href }) -> Remove the block call, can render a <link rel="prefetch"> wherever you link to the page
  • links({ data }) -> Use <Link prefetch="intent"> for { page } links you used with data and then inline <link /> inside your component based on the useLoaderData instead. Most uses of <link> are "body ok", so you can just render them inside the component instead.
  • Returning a string from actions for a redirect need to actually return redirect(string)

React Router v6.0.0-beta.6

Remix is now compatible with React Router v6.0.0-beta.6. We're days away from launching the stable v6 release over there! You must upgrade your react router dependency for Remix to continue to work properly.

Changes to actions

Actions don't require you to redirect out of them anymore! You can return responses just like loaders now. The data you return is available from useActionData(). This is especially nice for server side form validation errors: just return the errors as an object, no more session/action/loader dance!

import { useActionData, json } from "remix";

export function action({ request }) {
  let body = new URLSearchParams(await request.text());
  let name = body.get("visitorsName");
  return json({ message: `Hello, ${name}` });
}

export default function Invoices() {
  let data = useActionData();
  return (
    <Form method="post">
      <p>
        <label>
          What is your name?
          <input type="text" name="visitorsName" />
        </label>
      </p>
      <p>{data ? data.message : "Waiting..."}</p>;
    </Form>
  );
}

Note about resubmissions: Remix previously required redirects from actions to prevent accidental resubmissions (like booking a flight twice if the user clicks back). If you're rendering <Scripts/> the form will not be resubmitted on back or refresh so you're still protected automatically. However, now that you aren't required to redirect out of actions, Remix can't protect your users from resubmissions when you aren't rendering <Scripts/>. If you are handling forms without JavaScript, we highly recommend you still redirect out of your actions or ensure your actions can be run mutliple times without negative consequences.

Finally, since actions can return data, returning a string will no longer automatically redirect, it will send down the string as data. You'll need to wrap it in redirect(string) when upgrading.

Read more about useActionData

useLoaderData replaces useRouteData()

Because "route data" can come from both loaders and actions now, useRouteData didn't make a lot of sense so we've got two hooks now:

useLoaderData(); // data from your loader
useActionData(); // data from your action

useTransition replaces usePendingLocation and usePendingFormSubmit

With the transition rewrite, we've got a better hook that ecompasses all "pending" information. This hook tells you everything you need to know to build even better loading experiences. For example, you can indicate all phases of the pending form submission to the user. Previous we only knew it was pending and nothing more, now you know everything.

function SubmitButton() {
  let transition = useTransition();
  let text =
    transition.type === "actionSubmission"
      ? "Creating Record"
      : transition.type === "actionRedirect"
      ? "Redirecting to new record..."
      : "Create";
  return <button type="submit">{text}</button>;
}

Updating from the old hooks is pretty straightforward:

// old
usePendingFormSubmit();
// new
useTransition().submission;

// old
usePendingLocation();
// new
useTransition().location;

This hook also sets a solid foundation for us to finish our in-progress automatic scroll restoration, which should come very soon after this release.

There are numerous improvements to client side transitions that don't affect your code, but make your app better. In the case of interrupted navigations and form submissions, Remix previously simply ignored the responses of stale navigation fetches. Now it automatically aborts them using AbortController, saving your user's network bandwidth and the browser doesn't waste CPU cycles processing the response.

Read more about useTransition

Same URL data reloading and hash changes

Without JavaScript, if users click a link to the page they are already on, the browser will request a brand new document but replace the current entry in the history stack. Remix now emulates that behavior by refetching all loaders on the page and replacing the current entry in the history stack.

We also fixed a bug where loaders were called when only the url hash was changing. URL hashes don't go to the server so they no longer cause loaders to be called either, but they are a new location.

useFetcher

While Remix's loaders and actions are great for traditional navigations, modern apps often require more dynamic ways to communicate with the server. This hook enables you to call your loaders and actions outside of a navigation. You might think of it as using your loaders and actions as "API routes". Here are a few examples:

  • Writing a loader that returns data for a <Combobox> auto suggest component
  • A newsletter sign up form at the bottom of multiple pages in your app
  • Any UI where you need to allow multiple actions to be pending at the same time (like a list of records with single click buttons to change their state on the server)
  • Components that fetch data based on user interactions rather than navigation, like a user avatar that pops up their profile when hovered or focused.

Here's an example of marking an article as read:

function useMarkAsRead({ articleId, userId }) {
  let markAsRead = useFetcher();

  useSpentSomeTimeHereAndScrolledToTheBottom(() => {
    markAsRead.submit(
      { userId },
      {
        method: "POST",
        action: `/article/${articleID}/mark-as-read`,
      }
    );
  });
}

After the action completes, Remix will do its normal thing of reloading all loaders on the page after actions to ensure the data shown to the user is the latest data from the server. If multiple actions are pending at the same time, Remix makes sure to commit every fresh respnose and aborts any stale ones. That's right, Remix automatically takes care of race conditions!

Additionally, if you return a redirect from a loader/action being called by a fetcher, Remix will redirect the application to that page. And if any errors are thrown, the nearest error boundary will be rendered as usual. With useFetcher you get all of the same protections as a normal navigation when communicating with the server.

There are a lot more examples in the docs you should go check out:

Read more about useFetcher

unstable_shouldReload

During client side transitions, Remix will optimize reloading of routes that are already rendering, like not reloading layout routes that aren't changing. In other cases, like form submissions or search param changes, Remix doesn't know which routes need to be reloaded so it reloads them all to be safe. This ensures data mutations from the submission or changes in the search params are reflected across the entire page.

This function lets apps further optimize by returning false when Remix is about to reload a route. The most common case is telling Remix to never reload the root route:

export let loader = () => {
  return {
    ENV: {
      CLOUDINARY_ACCT: process.env.CLOUDINARY_ACCT,
      STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY,
    },
  };
};

export let unstable_shouldReload = () => false;

As always, Remix puts you in charge of the network tab.

Read more about shouldReload

Link Prefetching

This feature is awesome. One of our goals with Remix is to "destroy all spinners". Of course, we have a really great API to help you build great loading UI (useTransition), but the end goal is to not need the spinner in the first place. You can do that by link prefetching:

import { Link, NavLink } from "remix" // not react router!

// prefetch resources when the user seems like they're going to click it
<Link prefetch="intent" />

// prefetch it when this link renders
<NavLink prefetch="render" />

We recommend covering your app with `<Link pref...

Read more

v0.19.0-pre.1

07 Oct 22:50
Compare
Choose a tag to compare
v0.19.0-pre.1 Pre-release
Pre-release
Version 0.19.0-pre.1

v0.19.0-pre.0

07 Oct 22:02
Compare
Choose a tag to compare
v0.19.0-pre.0 Pre-release
Pre-release
Version 0.19.0-pre.0

v0.18.2

18 Sep 00:00
Compare
Choose a tag to compare

This release pins React Router to v6.0.0-beta.0 which fixes an issue we were having running Remix on the latest React Router beta release. We will update Remix to be able to run on the latest React Router beta releases next week.

v0.18.0

17 Sep 21:08
Compare
Choose a tag to compare

Well, summertime is winding up here in the Northern Hemisphere which means it's time for us to unleash everything we've been working on between family vacations and backwoods camping trips. And let me tell you: it's a LOT of stuff. Buckle your seat belts.

tl;dr Upgrade Guide

  • Upgrade the remix and all @remix-run/* packages to 0.18
  • Add remix setup to your package.json postinstall
  • Add { "paths": { "~/*": ["./app/*"] } } to your tsconfig's compilerOptions
  • If you're using TypeScript:
    • Sorry, you're gonna have to go read the part about our TypeScript improvements!
  • Enjoy the rest of your day

Where's the Transition Stuff?

First of all, I know that some of you have been following the work that Ryan released in v0.18.0-pre.0 and were expecting it to be released in v0.18, however that work is still being refined so we backed it out for this release. We expect it to be released in v0.19 very soon. We just didn't want it to block us releasing all the goodies we've had piling up while it settles.

Sourcemaps

The Remix compiler now outputs sourcemaps for your code on both the server and in the browser. Stack traces on the server will now give you a message in the console that links back to your original source code. This is especially handy when you're using a terminal emulator like iTerm 2 or VS Code that supports ctrl-click on file paths to open a file. Sourcemaps in the browser work as you'd expect.

Huge thank-you to kiliman who got the ball rolling on this one by patching the Remix source code!

Compiler Improvements

The Remix compiler now includes support for using ~ as an import alias for stuff in your app directory. We built this feature by piggy-backing on top of TypeScript's existing convention for path mapping, so the alias is actually defined in your app/tsconfig.json file (or app/jsconfig.json file if you're not using TypeScript).

This means that when app/routes/users/$id.tsx imports app/utils.ts, it can import utils from "~/utils" instead of import utils from "../../../utils".

Note: For now the only alias our compiler supports is ~. We may add support for more in the future.

To add support for this to an existing app, add the following to your tsconfig.json:

{
  "compilerOptions": {
    "paths": {
      "~/*": ["./app/*"]
    }
  }
}

Other things the compiler now supports include:

  • dynamic import()
  • importing .css files from packages in node_modules

We also fixed a bug where the compiler would crash when you had syntax errors and we now clean up all the files the compiler generates in dev mode when it shuts down.

TypeScript Improvements

A major goal for us is to have great support for TypeScript out of the box in Remix. This release further improves our TypeScript support by adding a remix.env.d.ts file to the project root. This will be present automatically in new projects created with npm init remix.

The remix.env.d.ts file references all the Remix types that you will need in your project. Previous to this release our types were made available to the TypeScript compiler whenever you imported something from remix. But if you didn't, you weren't able to use them.

If you're upgrading an existing project, add the following to your tsconfig.json:

{
  "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"]
}

And put the following in remix.env.d.ts in your root directory:

/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node/globals" />

Please note that we switched from using the node-fetch types in your node projects to using the built-in DOM types for things like Headers, Request, Response, and fetch. We took this approach because our route modules run in both the browser and on the server, so you'll most likely need the DOM types anyway. And since we can't overwrite the DOM types for these globals, it's better to piggyback on them.

If you need to do node-specific stuff, you can always type cast to get the type you need. For example, if you need to stream the body of an incoming request for doing things like file uploads, you could do something like this to get the right types:

export function loader({ request }) {
  ((request.body as unknown) as NodeJS.ReadableStream).pipe(...);
}

Note: We plan on having much better support for file uploads in an upcoming release, but this should get you by for now.

MDX

Remix v0.18 includes built-in support for handling MDX files, both as route modules (in app/routes) and in import statements. This is great for simple use cases of MDX where you don't have a lot of files.

Read more about our MDX support in the docs.

Netlify Functions

The npm init remix command now supports Netlify Functions as a development and deploy target. As usual, just follow the instructions in the README of your new Remix app to get up and running on Netlify in just a few minutes.

New remix setup Command

The remix CLI has a new command called remix setup for setting up your node_modules/remix directory with all the right files you need in your app.

When we introduced the remix package in v0.17, we relied on postinstall scripts in our various packages to automatically setup the node_modules/remix package with everything it needs so you don't have to remember where to import stuff from. Instead, you can just import it all from remix. However, separate postinstall scripts in our own packages are not guaranteed to run after you have installed everything you need for your app, including other @remix-run/* packages.

The remix setup command is a better solution. Instead of relying on our own package postinstall scripts, remix setup should run in your app's postinstall hook. This means it runs after all of your app's dependencies are installed. It should also be resilient to the way different package managers (npm, yarn, pnpm) do things when installing and removing packages.

This command is already included in the Remix init templates. If you have an existing app that you're upgrading, just add the following to your package.json:

{
  "scripts": {
    "postinstall": "remix setup"
  }
}

Support for Multiple Cookies

Remix v0.18 also adds support for multiple Set-Cookie headers on a single response. If you were trying to do this before, this will be a welcome upgrade.

Aaaaaaand, that's it! We hope you enjoy using this release of Remix as much as we've enjoyed making it for you.

v0.17.0

17 Sep 21:09
Compare
Choose a tag to compare

This release brings us a giant step closer to v1 stable by introducing a new remix package that you'll use in all your app code. It also removes the old compiler from @remix-run/dev and completely replaces it with the new one we introduced in v0.15. Finally, we introduced a new <LiveReload> element that replaces our useLiveReload() hook.

There are a few breaking changes in this release, so we have bumped the minor version accordingly. This will likely be the last release of the 0.x series before we move to 1.x beta releases.

New remix Package

The major new feature in this release is the new remix package, which is a significant improvement in the way you use Remix in your app code. We recommend using the remix package for all your imports instead of importing directly from @remix-run/node and/or @remix-run/react. The remix package contains all the exports you need from both of those packages.

// You can replace these:
// import type { LoaderFunction } from "@remix-run/node";
// import { useRouteData } from "@remix-run/react";
// With this:
import type { LoaderFunction } from "remix";
import { useRouteData } from "remix";

export let loader: LoaderFunction = () => {
  return { now: Date.now() }
};

export default function HomePage() {
  let { now } = useRouteData();
  return (
    <p>This page was rendered at {new Date(now).toLocaleString()}.</p>
  );
}

Now you don't have to remember which package to get stuff from, which was kind of a pain in the past 🤪

If you start a new app today using npm init remix, you will automatically get { "dependencies": { "remix": "*" } } in your package.json. If you're upgrading an existing app, you'll want to add the remix package to your package.json dependencies:

$ npm add remix@*

Streamlined CLI Commands

We had a proliferation of CLI commands when we introduced the new compiler in v0.15, and then again when we added our own built-in app server in v0.16. But in v0.17, it all comes into focus! 🧐

We now have 3 remix CLI commands:

  • remix build - Runs the compiler and generates the build. This uses our new esbuild-based compiler, and was previously remix build2.
  • remix dev - Runs the compiler in watch mode and boots the dev server for live reloading. This was previously remix run2.
  • remix run - Runs the built-in application server (requires @remix-run/serve) + remix dev. This was previously remix run3.

These commands are designed to provide the right level of functionality in several different usage scenarios. The story goes something like this:

  • If you're using remix-serve to deploy your app in production, use remix run in development. It's the same server plus everything that remix dev does.
  • If you're using Architect/Vercel/Firebase or @remix-run/express in your own node server.js, use remix dev in development. You'll have to run 2 processes either in 2 separate terminal tabs or using a process manager like pm2-dev.

We are very happy to finally have some resolution here. It got crazy there for a second 😅

New <LiveReload> Element

This release introduces a new <LiveReload> element that replaces the useLiveReload() hook we shipped in v0.16. Having an element instead of a hook is a little more ergonomic since the rules of hooks require you to always use them, but you aren't always in dev. The <LiveReload> element also does not require you to render a <Scripts> element or hydrate the page, so it works when you are developing a page without any other scripts.

If you were using useLiveReload() previously, replace it with a <LiveReload> element in the same component.

import { LiveReload } from "remix";

export default function MyApp() {
  // Instead of this:
  // useLiveReload();

  return (
    <Document>
      <p>Welcome to the app!</p>

      {/* Use this: */}
      {process.env.NODE_ENV === "development" && <LiveReload />}
    </Document>
  )
}

We hope you enjoy this release as much as we've enjoyed making it for you. Onward to 1.0!

v0.16.0

17 Sep 21:13
Compare
Choose a tag to compare

This release gives a taste of what it will be like after our 1.0 release: no breaking changes just improvements and bug fixes 😎

New Features

*.server.js and *.client.js are back!

The new esbuild-based compiler now supports the *.server.js and *.client.js extensions to exclude modules from the opposite build. *.server.js files will only be included in the server bundle and *.client.js files will only be included in the browser bundles.

This allows you to have module side-effects like reading window in client only files:

// ./stripe.client.js
let stripe = createStripe(window.ENV.STRIPE_PUBLIC_KEY);
export { stripe };

Before that would have thrown an error while server rendering, but now it will be excluded from the server build.

Or if you've got node_modules that are doing dynamic requires or have module side-effects that are messing up your browser bundles, you can just eliminate them from the browser bundles by putting those imports into a *.server.js file.

// ./problematic-thing.server.js
import { stuff } from "problematic-thing";
export { stuff };

Now that file will be excluded from client bundles and your problems go away.

npm init remix

Goodbye starters, hello generator!

Instead of cloning starter repos and fiddling around with an .npmrc, we have a new project generator. Give it shot! open up a terminal and type:

npm init remix

It will ask you where you want to deploy, will auto-detect your Remix token in a home ~/.npmrc or REMIX_TOKEN environment variable, ask if you want TypeScript or JavaScript, and then generate the project for you.

remix-serve

If you already tried out npm init remix you may have noticed the first server option was "Remix App Server".

We've been saying the whole time that "Remix isn't your server, it's just a function on your server". Now Remix can also be your server.

npm i @remix-run/serve
remix-serve <server-build-dir>

This is a small, but production-ready, express server for your Remix app. Not only is it great for production, but it makes starting a new project when inspiration strikes frictionless. Check it out:

  1. You get an idea!
  2. npm init remix
  3. npm run dev

And then you can deploy that app easily to places that run node apps like Heroku, Fly, Render, Google App Engine, AWS, etc.

remix dev and remix run3

Yep ... run3. 🤣 We'll explain in a second, but first here's what it does.

Now that we have a Remix App Server, we can have a command that runs both the app server and the dev asset server in the same process (no messing around with multiple tabs or concurrently/pm2-dev).

And that's exactly what run3 does for apps using the Remix App Server!

Okay, let us explain the name:

When we released the new esbuild-based compiler, we added run2 and build2. In the release after this we will be removing the old compiler. Then we can clean up this cli's hilarious command names.

In the next release there will only be three commands:

  • remix build - builds your app for production (today's build2)
  • remix dev - starts the asset dev server and rebuilds on file changes (today's run2)
  • remix run - runs app and dev asset server in the same process in develoment mode (today's run3)

You can use remix dev today instead of run2.

useLiveReload

Running remix dev now supports live reload! When you change files the browser will automatically reload the page. This is a big productivity enhancement.

You have complete control over using this feature or not:

import { useLiveReload } from "remix";

export function Root() {
  useLiveReload();
  return <html>{/* ... */}</html>;
}

It will only connect to the dev server in development mode, so you don't have to worry about removing the hook's call site in production.

People are curious about HMR after this. It will likely happen but not anytime soon. HMR in React is heavily dependent on babel which our new compiler doesn't use. We've explored it and know the amount of effort it will take, so we'll be waiting until our business can afford the investment in a marginal improvement over live reload.

Fixes

useSubmit now works as expected when not passing in a form or button element.

v0.15.0

17 Sep 21:14
Compare
Choose a tag to compare

This release introduces an experimental new compiler based on esbuild, which is much faster than Rollup. In our tests, we've found the new compiler to be anywhere from 20-100x faster than the current compiler.

To help with migrating to the new compiler, this release ships with both our current compiler and the new one. So you can upgrade your app to Remix 0.15 and run it on the current compiler, then try out the new compiler and see what breaks. We think you'll enjoy the speed improvements so much that you'll want to switch as soon as possible. The time you spend making the migration will easily be worth the time you won't spend waiting on future builds 😅

There are also a few minor changes to a few of our packages that are going to help us get onto more platforms in the future. Right now Remix runs only on node. But as more JavaScript runtimes appear on different cloud providers (hello Cloudflare Workers!) we are going to run Remix on all of them.

Upgrading Summary

To upgrade from Remix 0.14:

  • Replace @remix-run/data with @remix-run/node in your package.json dependencies
    • npm remove @remix-run/data
    • npm add @remix-run/node
  • Change all @remix-run/data imports to @remix-run/node
  • Remove deep imports from @remix-run/react:
    • In app/entry.client.js, change import Remix from "@remix-run/react/browser" to import { RemixBrowser as Remix } from "remix"
    • In app/entry.server.js, change import Remix from "@remix-run/react/server" to import { RemixServer as Remix } from "remix"

Please see the "Notes on Package Changes" section below for background on these changes.

As always, remember to use the starters as your guide. We currently have starter repos for:

Using the New Compiler

Note: Before you attempt this, please be sure you follow the notes in the Upgrading Summary section (above) first! Once you get your app working with the current compiler, come back here and try to use the new one.

This release ships with a new experimental compiler based on esbuild. We have been blown away by how fast esbuild is, and we think you're going to really enjoy the speed improvements too. However, there are some changes you'll need to make to your app in order to use it.

The first thing you'll need to change is the CLI command you use to invoke the compiler. In this release, remix build will still invoke the old compiler and remix build2 will invoke the new one. Eventually remix build2 will become remix build and we'll remove the old compiler entirely. This is the same for remix run and remix run2. You can also use remix watch2 if you just want to run the build in watch mode without firing up the dev server.

The new compiler does not support:

  • .client.js and .server.js file extensions
  • url:, css:, and img: imports
  • .mdx route modules

I know that's a lot to drop on you in a single release, but that's why we're calling this compiler "experimental"! Please allow me to explain in greater detail how we are thinking about each of these features.

Client/server-only Modules

In v0.13 we introduced the .client.js and .server.js file naming scheme for manually hinting to the compiler which files to include in the build. We used these hints as a way to try and speed up the compiler by giving hints to Rollup about which files it needed to include in the build, but esbuild is so fast that we don't need them anymore.

We still rely on tree-shaking to get rid of server-only code in your client bundles, so that hasn't changed. The main difference is that esbuild uses the sideEffects: false flag in package.json to determine whether a module has side effects or not. This is a webpack-ism that has been around for a few years now, so it should already be supported in all the packages you need in your app. However, you will need to add that field to your app's package.json in order for esbuild to eliminate your server-only modules from the client bundles.

Add this to your app's package.json:

{
  "sideEffects": false
}

If you have browser-only code, instead of using a .client.js file extension, you can make sure it won't run on the server using a traditional typeof window guard:

if (typeof window !== "undefined") {
  // browser-only stuff goes here
}

URL, CSS, and Image Imports

The new compiler includes support for importing many different types of files as URLs including SVG, fonts, CSS, and images. When you import a file, it will be copied to the build output and you will get a cache-able (hashed) URL to the asset.

import logoImageUrl from "../images/logo.png";

function Logo() {
  return <img src={logoImageUrl} />;
}

We are still working out how we'd like to support CSS and images. esbuild includes several different loaders for different content types, and they are working on native support for CSS and CSS modules, so we are following that work closely to see how it pans out.

As for images, we are still working out how we can best support all the different sources of images without slowing down the compiler. There are many different sources for images, but the img: import strategy only really works for image files that are stored alongside your source code. That won't be the case if you have a lot of images or if they are generated by users. So we are rethinking how to best handle these in the new compiler. There are also many different ways to serve images. Services like Akamai and Cloudinary are popular choices for hosting images, and they make it really easy to generate different formats. CDNs like Fastly and Cloudflare also have image optimization capabilities, and it's also very compelling to be able to build them on the fly as needed instead of building it into the build step of your app.

For now, when you import a .css or image file using the new compiler, you'll get a hashed URL to that file. We will also be writing up some detailed guides about the various strategies for handling CSS and images in Remix apps using the new compiler. The guide to using PostCSS in Remix is already a good start.

MDX Route Modules

The new compiler does not support using .mdx files as route modules. We are planning on re-introducting first-class support for .mdx files as route modules as soon as possible.

In the mean time, if you're using MDX one project you might be interested in is Kent C. Dodds' mdx-bundler. It's a fast tool (also based on esbuild 🙌) that will bundle up your MDX for you and give you the code you need to actually render your component. You could possibly move your MDX out of the app/routes directory and into some other directory like app/pages. The new compiler will give you the text of the file (instead of a URL) when you import text from "../pages/something.mdx", so you could do something like this:

import { bundleMDX } from "mdx-bundler";
import { getMDXComponent } from "mdx-bundler/client";
import { useRouteData } from "remix";

// You can get the MDX from your own filesystem. But if you have a lot
// of content you're probably going to get this from a database somewhere.
import text from "../pages/something.mdx";

export async function loader() {
  // You could load MDX from a database here!
  // let text = await getText("https://github.com/my/repo/posts/something.mdx");
  return await bundleMDX(text);
}

export default function MyPage() {
  let { code } = useRouteData();
  let Component = React.useMemo(() => getMDXComponent(code), [code]);
  return <Component />;
}

We believe this is a great way to handle MDX; as content instead of source code. Now you don't have to slow down your build compiling a bunch of MDX files!

Done!

Aaaaaaand that should be it if you'd like to try out the new compiler. Please let us know if we missed something so we can add it to these notes. And please let us know how it goes so we can continue to improve the new compiler!

Notes on Package Changes

In the last release (0.14) and this one some packages have moved around, and I thought it'd be nice to put a few notes here about how we are thinking about structuring our packages going forward so it's clear why we are making these changes. Hopefully this will clarify how we are thinking about supporting Cloudflare Workers in the near future as well.

We currently ship three separate packages that run on node: @remix-run/architect, @remix-run/express, and @remix-run/vercel. Each package shares a common dependency, @remix-run/node, and exports a createRequestHandler function that is suited for working with that particular provider's HTTP server API.

In this release, we eliminated the @remix-run/data package and elevated @remix-run/node to an app-level dependency (in 0.14 it was a transitive dependency called @remix-run/core) to more accurately reflect its target runtime. So if you're building a node app with Remix, your app-level dependencies are:

  • @remix-run/node (the "environment" you're running in)
  • Your "provider" (currently @remix-run/architect, @remix-run/express, or @remix-run/vercel)
  • @remix-run/react

We will follow this same pattern to support other JS runtimes in the near future. So e.g. if you're running your app in Cloudflare Workers, the packages you'll need will be (names may change):

  • @remix-run/service-worker
  • @remix-run/cloudflare-workers
  • @remix-run/react

Hopefully that clarifies how we are thinking about structuring dependencies going forward! Thanks for your patience as we work this out.

v0.14.0

17 Sep 21:14
Compare
Choose a tag to compare

This release includes some significant improvements to the way Remix apps are deployed to production. It includes two major improvements to a production Remix app:

  • No more dev-only dependencies being deployed to your server
  • No more dynamic requires

These are some pretty major changes to the underlying architecture of Remix that fixes support for several cloud providers and opens the door to deploying to many more.

Specifially, this release fixes deployments on both Architect (broken in 0.10) and Vercel (who recently changed the way they do deploys).

Upgrading Summary

To upgrade from Remix 0.13:

  • Remove @remix-run/cli from your dependencies in package.json and replace it with @remix-run/dev in your devDependencies
  • Use createRequestHandler({ build: require("./build") }) in server.js
  • Add a postinstall step to run remix build
  • In dev mode, watch build/assets.json to know when to restart the server

As always, remember to use the starters as your guide. We currently have starter repos for:

Background

Before Remix 0.14, we had both development and production dependencies in the same package: @remix-run/core. Having everything in one package helped us bootstrap the project quickly, but also became a burden over time. For example, as we added features to the Remix compiler, which is only needed in development, the overall size of the dependencies Remix needed in a production deployment grew. So, we knew we needed to split out all of Remix's development dependencies into a separate package. In addition, some of our dev dependencies require binaries that we are not able to deploy to AWS. Separating them out into a dev-only package fixes production deploys on AWS.

Another issue with deploying Remix to production before this release is that it required you to deploy your remix.config.js file (and your app directory!) alongside your build directory. Remix would read the config and reconstruct the route hierarchy at runtime so that it knew about any dynamic routes you created using config.routes. It would also dynamically require all modules needed to run your app, which was a non-starter on hosts like Netlify (and, suddenly as of this week, Vercel 🤷‍♂️). We knew we needed to get rid of all dynamic requires in production. You shouldn't have to deploy your source code, just the build artifacts.

So ... this release splits up our core dependency and completely alters the way we load modules and deploy to production.

No sweat, right? 😅

The big winner is that your production deploys are going to be more streamlined (no dev dependencies) and we are laying the groundwork for being able to deploy Remix to more providers by eliminating dynamic requires.

Upgrading

Despite the significant changes on our side, upgrading your app from Remix 0.13 should be fairly straightforward.

First, remove @remix-run/cli from your dependencies in package.json and replace it with @remix-run/dev in your devDependencies.

$ npm remove @remix-run/cli
$ npm add -D @remix-run/dev

Going forward, @remix-run/dev will contain everything you need to develop a Remix app. It should only ever be in devDependencies so it doesn't end up in your production environment. We even named it dev to help you remember :D

Next, we need to let the Remix server know about our app. In Remix 0.14, your entire app is compiled down to a single module in build/index.js with static require calls to load the rest of the app. This means you can load the entire app using require("./build") (or whatever your config.serverBuildDirectory is).

To upgrade, open up server.js (or your serverless function module) and add the build key to your server's createRequestHandler:

createRequestHandler({
  // Add this line!
  build: require("./build"),
  getLoadContext() {
    // ...
  }
});

Note: You can see how we did it in the Express starter.

This line requires the entire app and passes it to your server. Of course, this means you'll need to actually build the app before you can start the server, so you may want to add a postinstall hook to your package.json so that your app is ready to go after a fresh npm install:

{
  "scripts": {
    "postinstall": "remix build"
  }
}

If you're using Vercel, Architect, or Firebase, this postintall hook isn't necessary, just make sure your build finishes before you open the app in the browser.

In Express apps, you'll probably also want to update your file watcher in development. We currently recommend watching build/assets.json to know when to restart your server.

In the Express starter we use PM2. The relevant portion of the config looks like this:

module.exports = {
  apps: [
    {
      script: "server.js",
      watch: ["build/assets.json"]
    }
  ]
};

Note: There are actually 2 builds going on (server and client), but the client build always takes longer since it has to bundle dependencies as well as your app code. So that's why we recommend watching build/assets.json. We are, however, working on improving our build times, so this recommendation may change soon.

If you're using Vercel or Architect, you don't need to set up any watchers, these providers dev servers already handle the file watching.

Vercel Notes

The build config and remix config in Vercel deploys were a bit involved because of our dynamic requires, you can now greatly simplify it, and even use Vercel's auto-deploys from GitHub now.

Your remix.config.js no longer needs to branch on any environment variables, it can be simple again:

module.exports = {
  appDirectory: "app",
  browserBuildDirectory: "public/build",
  publicPath: "/build/",
  serverBuildDirectory: "build",
  devServerPort: 8002
};

Your vercel.json can likewise be simplified by removing the includeFiles option in your builds config:

{
  "builds": [
    {
      "src": "index.js",
      "use": "@vercel/node"
    }
  ],
  // etc.
}

Because your app modules are all in the require graph now, we don't need to provide any hints to Vercel about what to deploy, it knows just by looking at your index file.

Other Changes

A few other miscellaneous changes in this release that you may be interested in:

  • The compiler now uses an on-disk cache that defaults to the .cache directory in the root (sibling to remix.config.js). If you want to put it somewhere else, use config.cacheDirectory in your remix.config.js
  • The compiler doesn't replace any process.env.NODE_ENV strings in your server code anymore, so you should get that value from the actual process that is running your server

Enjoy your slimmed down production builds!