Skip to content

Releases: remix-run/remix

v0.20.0

29 Oct 22:00
Compare
Choose a tag to compare

Gather 'round folks, this release brings along lots of tasty features and stomps out a few gnarly bugs. Here's a high-level overview of important changes:

  • CLI changes
    • remix dev is now remix watch
    • remix run is now remix dev
    • Added remix routes
    • Added remix build --sourcemap
  • <Form forceRefresh /> is now <Form reloadDocument />
  • Added a new handleDataRequest API for manipulating request data
  • Now using react-route-dom@6.0.0-beta.8

Now, let's get into to the weeds a bit 🤓

🕞 tl;dr Upgrading

  • Update your package.json or pm2 configs
    • remix dev -> remix watch
    • remix run -> remix dev
  • <Form forceRefresh /> -> <Form reloadDocument />
  • Ensure you've got react-route-dom@6.0.0-beta.8 in your package.json dependencies.

💔 Breaking Changes

  • Several users felt that the remix dev command did not behave as expected. The CLI command remix dev has been changed to remix watch, and remix run has been changed to remix dev to hopefully make the CLI a bit more intuitive. Please note that remix dev requires that @remix-run/serve is installed as a dependency, just as remix run did before (#315).
  • The forceRefresh prop on <Form> has been renamed to reloadDocument. We feel like this more clearly communicates the behavior of the form's submission.

✨ Improvements

  • Now shipping with the latest beta of react-router-dom! 🚀

  • We updated all docs and templates to suggest npx create-remix instead of npm init remix, which is generally more consistent and less error prone for creating new sites with reasonable defaults.

  • TypeScript should now correctly infer types from imported .json modules (#314)

  • Added support for multiple meta tags that use the same key to a route's meta function (#322). We now allow you to pass an array of values to a key in the object returned by meta:

    export let meta: MetaFunction = () => {
      return {
        "article:author": ["Chance the Dev", "Ryan Florence"],
      };
    };
  • We added a new CLI command remix routes that allows you to visualize your app's route structure from the command line in either JSX or JSON format (#326).

  • We introduced handleDataRequest for modifying the response of data requests in routes (#329):

    // example route file
    import type { HandleDataRequestFunction } from "remix";
    
    // this is an optional export!
    export let handleDataRequest: HandleDataRequestFunction = (
      response,
      { request, params, context }
    ) => {
      // do whatever you'd like before returning the response!
      response.headers.set("x-custom", "yay!");
      return response;
    };
  • remix build no longer puts source maps in your browser build by default. This was a security issue because people could see your server side code from the browser! If you want to have source maps in your production builds, you can use the --sourcemap flag 🗺️ (#350).

  • We made a handful of improvements to the new Remix application templates.

🐛 Bugfixes

  • The ~/ path alias for the app directory now works when importing markdown files (#317).
  • We squashed a bug in which form submissions were trimming values with duplicate keys (checkboxes, radio inputs, etc.) (#344).
  • We annihalated a bug in which redirects are not followed when thrown from an action for client-side form submissions (#349).
  • We exterminated a particularly nasty bug in the @remix/architect server that was sending requests to invalid URLs (#333).
  • We wiped out several bugs for sites shipping to Cloudflare Workers (#318).
  • We crushed a bug causing back/forward navigation mayhem (#351)
  • We demolished a bug where <Link prefetch> was prefetching way too much stuff.
  • We fumigated a bug where imports from React Router weren't working in Remix (like <Outlet>).
  • I'm running out of creative synonyms for "fix", so thankfully I'm also out of bug fixes to describe 😅

🗒️ Docs

  • Lots of docs updates and improvements, perhaps too many to capture succinctly in release notes! Be sure to check out https://docs.remix.run for the latest and greatest.

v0.19.3

14 Oct 14:19
Compare
Choose a tag to compare

Couple bug fixes:

  • Build no longer fails on layout routes with actions
  • Can use npm 7+

v0.19.2

10 Oct 04:08
Compare
Choose a tag to compare

Changed layout route convention from _layout to two underscores: __layout. This is a breaking change but it's only been around for 24 hours.

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 react-router-dom@6.0.0-beta.6
  • 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!