Releases: remix-run/remix
v0.20.1
v0.20.0
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 nowremix watch
remix run
is nowremix 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
[email protected]
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
[email protected]
in your package.json dependencies.
💔 Breaking Changes
- Several users felt that the
remix dev
command did not behave as expected. The CLI commandremix dev
has been changed toremix watch
, andremix run
has been changed toremix dev
to hopefully make the CLI a bit more intuitive. Please note thatremix dev
requires that@remix-run/serve
is installed as a dependency, just asremix run
did before (#315). - The
forceRefresh
prop on<Form>
has been renamed toreloadDocument
. 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 ofnpm 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 bymeta
: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 theapp
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
Couple bug fixes:
- Build no longer fails on layout routes with actions
- Can use npm 7+
v0.19.2
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
- Type fixes
EntryContext
Route
LinksFunction
- Bugfixes
- Routes without loaders were being called on client side transitions
v0.19.0
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 pagelinks({ data })
-> Use<Link prefetch="intent">
for{ page }
links you used with data and then inline<link />
inside your component based on theuseLoaderData
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.
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.
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:
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.
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...
v0.19.0-pre.1
Version 0.19.0-pre.1
v0.19.0-pre.0
Version 0.19.0-pre.0
v0.18.2
v0.18.0
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.jsonpostinstall
- Add
{ "paths": { "~/*": ["./app/*"] } }
to your tsconfig'scompilerOptions
- 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 innode_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.