Releases: remix-run/remix
v1.9.0
The New Stuff
Support for React Router's Optional Route Segments
We shipped the latest minor version of 6.5.0 with support for optional route segments, and now Remix supports them as well. To do this, we've introduced a new convention for file-system routes.
Route filenames surrounded by parenthesis will be converted into optional segments for React Router. For example /($lang)/about
will be converted to /:lang?/about
.
This means /($lang)/about
would match:
/en/about
/fr/about
/about <-- $lang is optional!
Another example: /(one)/($two)/(three).($four)
route would match all of the following:
/
/one
/one/param1
/one/param1/three
/one/param1/three/param2
As with any of our conventions, you can escape the conversion by wrapping the route filename in square brackets. For example, /[(one)]/two
would match the URL path /(one)/two
.
Added Support for New TypeScript Syntax
The Remix compiler now supports new TypeScript 4.9 syntax (#4754). There were several cool features that landed in the latest TypeScript release, and we're stoked that you can grab them today! 🤓
One of our favorites is the satisfies
keyword, which lets you validate that an expression matches a given type—without changing the resulting type of that expression.
// this example comes from the TypeScript 4.9 release notes
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ~~~~ The typo is now caught!
} satisfies Record<Colors, string | RGB>;
// Both of these methods are still accessible!
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();
For a closer look at all the new features available, check out the TypeScript release notes.
Perf Gains for Your Routes 💪
Sometimes you can get big wins out of tiny changes. We did that by making a tweak to a lookup algorithm in defineConventionalRoutes
that resulted in some numbers we love to see.
In local runs of the production builds for a larger, realistic project (~700 routes):
- Previously: 10-15s
- Now: <1 second — >10x faster!
In addition to new features, we also squashed some nasty critters over the last week.
A Pesky Bug Squashed: Firefox and <LiveReload>
We fixed up a problem with <LiveReload>
in Firefox that caused pages to infinitely reload after changes. This was no bueno!
The problem was:
- Firefox is calling
ws.onclose
immediately upon connecting (?!) - Then we’re trying to reconnect, and upon reconnection, we reload the page
- Firefox then calls
ws.onclose
again after reconnecting and the loop starts over
This fix is to check for the proper event code (1006
) before actually trying to reconnect and the reload the page. 1006 means the connection was closed abnormally, but in our case it means the server was shut down in local dev, and the socket can reconnect again when the server is back up.
Changes by Package
New Contributors
- @lucasferreira made their first contribution in #3970
- @dabdine made their first contribution in #4734
- @dmarkow made their first contribution in #4538
- @lordofthecactus made their first contribution in #4706
- @akamfoad made their first contribution in #4825
- @jsbmg made their first contribution in #4833
Full Changelog: v1.8.2...v1.9.0
v1.8.2
What's Changed
All the small things:
- Remove
instanceof Response
checks in favor ofisResponse
(#4782) - Fix performance regression with creation of
@remix-run/router
static handler (#4790) - Update dependency for
@remix-run/router
tov1.0.5
(bd84a9317
)
Full Changelog: v1.8.1...v1.8.2
v1.8.1
What's Changed
Patch Changes
- Added a missing type definition for the Remix config
future
option to the@remix-run/dev/server-build
virtual module that caused type-checking to fail in some runtime adapters (#4771)
Changes by package
New Contributors
- @roj1512 made their first contribution in #4591
- @eltociear made their first contribution in #4477
- @AustinGil made their first contribution in #4283
- @mjangir made their first contribution in #4625
- @chaukhoa97 made their first contribution in #4688
- @shubhaguha made their first contribution in #4745
- @pmbanugo made their first contribution in #4767
Full Changelog: v1.8.0...v1.8.1
v1.8.0
What's Changed
Lots of goodies to dig in this week, so let's get right to it 💪
React-Routering Remix
We released React Router 6.4 a few weeks back. This update introduced many of Remix's data APIs and moved all of the core routing logic to a framework-agnostic @remix-run/router
package. As a result, we needed to rebuild the Remix implementation to take advantage of these changes.
1.8.0 includes the first big step in that process, and Remix now performs all server-side data fetches and mutations through the new framework agnostic router. This is an implementation detail and doesn't change much for you today, but completing this work will open the door for Remix to support other frameworks in the future. 🤯
Our guy @brophdawg11 published React-Routering Remix to dive into the details, so we encourage you to check it out to get a better glimpse of things to come!
A more robust meta
API
Each Remix route can export a meta
function that will render <meta>
elements in the head of the route's document. The existing meta
API was designed to simplify some of the inconsistencies of various meta tag attributes, but in turn it made some meta tags more difficult—if not impossible—to use correctly.
We also automatically merged all meta tags in nested route trees. While this is desirable in many cases, it's not a decision we should make for you in the cases where a leaf route's meta should omit its parent's tags.
As a result, we are rolling out a new, lower-level API for meta
. Now you will return an array of objects that map directly to the <meta>
tag's attributes, exactly as we do for links
. You also get access to the route matches with meta and data available so you can decide how you want to handle merging meta tags.
export function meta({ data, matches }) {
// Want to snag some meta from a matched route? No problem!
let rootModule = matches.find((match) => match.route.id === "root");
// Only want to merge its og: tags? Easy breezy!
let rootOgTags = rootModule.meta.filter((meta) =>
meta.property?.startsWith("og:")
);
// Merge what you want, where you want. No more magic!
return [
...rootOgTags,
{ title: "All Downhill from Here" },
{ property: "og:type", content: "music.song" },
{ property: "music:musician", content: "https://www.newfoundglory.com/" },
{ property: "music:duration", content: 192 },
{
property: "music:album",
content: "https://open.spotify.com/album/1Igrcji3zf5aC61saylDE1",
},
];
}
While this is a new API, the change is completely opt-in via the new future
option in remix.config.js
and it will become the default behavior in version 2.0. No breaking changes for you, but a clear and early upgrade path should you choose to take it! To opt in, add the following to your Remix config file:
module.exports = {
future: {
v2_meta: true,
},
};
For more details on how we got here, check out the original RFC.
Making thrown Response
objects less ambiguous
Previously there was some ambiguity around "thrown Responses go to the CatchBoundary
". The CatchBoundary
exists to give the user a place to handle non-happy path code flows such that they can throw Response
instances from their own code and handle them in a CatchBoundary
. However, there are a handful of framework-internal errors that make sense to have a non-500 status code, and the fact that these were being thrown as Response
instances was causing them to go into the CatchBoundary
, even though they were not thrown by the user.
With this change, anything thrown by the framework itself (Error
or Response
) will go to the ErrorBoundary
, and any user-thrown Response
instance will go to the CatchBoundary
. There is one exception to this rule, which is that framework-detected 404 responses will continue to go to the CatchBoundary
since users should have one single location to handle 404 displays.
The primary affected use cases are scenarios such as:
- HTTP
OPTIONS
requests (405 Unsupported Method ) GET
requests to routes without loaders (400 Bad Request)POST
requests to routes without actions (405 Method Not Allowed)- Missing route id in
_data
parameters (403 Forbidden) - Non-matching route id included in
_data
parameters (403 Forbidden)
For more information on this change, check out the PR in which it was introduced.
Other Changes
- Add support for importing
.wasm
files (#3299) - Update
@remix-run/web-fetch
. This addresses two bugs: (#4644)- It fixes a memory leak caused by unregistered listeners
- It adds support for custom
"credentials"
values (Remix does nothing with these at the moment, but they pass through for the consumer of the request to access if needed)
- Ensure route modules are loaded even in failure cases
- This addresses a long standing issue where you would end up in your root catch boundary if a form transition to another route threw. This no longer occurs, and you end up in the contextual boundary you'd expect. (#4611)
Changes by package
New Contributors
Full Changelog: v1.7.6...v1.8.0
v1.7.6
What Changed
Patch Changes
- Fixed a regression in the browser build for browsers that don't support the nullish coalescing operator (#4561)
Changes by package
New Contributors
- @wangbinyq made their first contribution in #4287
- @aissa-bouguern made their first contribution in #4465
- @lili21 made their first contribution in #4017
- @luisrivas made their first contribution in #4490
Full Changelog: v1.7.5...v1.7.6
v1.7.5
What Changed
Patch Changes
- Make sure namespaced Open Graph and
fb:app_id
meta data renders the correct attributes on<meta>
tags (#4445) - Add the missing event parameter type to
useBeforeUnload
(#1723)
Changes by package
Full Changelog: v1.7.4...v1.7.5
v1.7.4
What Changed
Patch Changes
-
You can now infer the type of the
.data
property of fetchers from youraction
andloader
functions. This works similarly to our type inference ofuseLoaderData
by providing the type of theaction
orloader
function at theuseFetcher
call site.export async function loader(args: LoaderArgs) { return json({ user: { name: "Chance", twitter: "@chancethedev", age: 36, }, }); } function SomeComponent() { let fetcher = useFetcher<typeof loader>(); if (fetcher.data) { let userName = fetcher.data.user.name; // string let userAge = fetcher.data.user.age; // number } }
-
Fixed a bug in
<Form>
that prevented the correct method from being called with non-POST
submissions
Changes by package
New Contributors
- @michaelhelvey made their first contribution in #4392
- @maxschwarzmueller made their first contribution in #4413
Full Changelog: v1.7.3...v1.7.4
v1.7.3
What Changed
Patch Changes
-
<Form />
now respects theformMethod
attribute set on the submitter element (#4053)<Form> <button type="submit">GET request</button> <button type="submit" formMethod="post"> POST request </button> </Form>
-
Assets referenced in CSS files are now hashed and copied to the
assetsBuildDirectory
(#4130).Before this change, a CSS declaration like
background: url('./relative-path/image.png')
that references the file./relative-path/image.png
will not copy that file to the build directory. This can be a problem if you use a custom build directory, or when dealing with third-party stylesheets innode_modules
that reference their own relative files. -
We updated the
@remix-run/web-fetch
dependency for@remix-run/node
(#4277). This fixes issues with{Request | Response}.clone()
throwing when body isnull
. This update also adds additional Node.js-specific types tofetch()
to support the use ofagent
fromhttp
andhttps
. -
Added support for setting
moduleResolution
tonode
,node16
ornodenext
intsconfig.json
(#4034) -
Added resources imported only by resource routes to
assetsBuildDirectory
(#3841)
Changes by package
New Contributors
- @markdalgleish made their first contribution in #4173
- @wtlin1228 made their first contribution in #4224
- @ryanjames1729 made their first contribution in #4241
- @runofthemill made their first contribution in #4268
- @kamtugeza made their first contribution in #4041
- @freeman made their first contribution in #4159
- @johnmberger made their first contribution in #4329
- @kevlened made their first contribution in #4053
- @KingSora made their first contribution in #4130
Full Changelog: v1.7.2...v1.7.3
v1.7.2
What's Changed
Bug Fixes
- Preserve
?index
for fetcher get submissions to index routes (#4238) - Fix dependency conflicts with
type-fest
(87642b71b
) - Update ESLint and plugin dependencies (
e4ec81c77
) - Flush Node streams to address issues with libraries like
compression
that rely on chunk flushing (#4235)
Changes by package
Full Changelog: v1.7.1...v1.7.2
v1.7.1
What's Changed
Bug fixes 🐛
- Ensured that requests are properly aborted on closing of a
Response
instead ofRequest
in all Node adapters (#3626) - Locked the dependency on
react-router-dom
to version 6.3.0 instead of using a semver range (#4203), as installingreact-router-dom@6.4.0
will cause compatibility issues - Fixed a bug with
GET
form submissions to ensure they replace the current search params, which tracks with the browser's behavior (#4046)
Changes by package
@remix-run/architect
@remix-run/express
@remix-run/netlify
@remix-run/react
@remix-run/server-runtime
@remix-run/vercel
New Contributors
- @scottybrown made their first contribution in #3949
- @rkulinski made their first contribution in #4003
- @jrf0110 made their first contribution in #4050
- @jwnx made their first contribution in #3936
- @shairez made their first contribution in #3962
- @jmorel88 made their first contribution in #4066
- @willhack made their first contribution in #4147
- @riencoertjens made their first contribution in #4158
- @arjunyel made their first contribution in #4133
- @Deanmv made their first contribution in #4170
- @leifarriens made their first contribution in #4181
Full Changelog: v1.7.0...v1.7.1