Lazy Route Discovery: Sort /__manifest
query parameters for better caching (#9888)
Single Fetch: fix revalidation behavior bugs (#9938)
- With Single Fetch, existing routes revalidate by default
- This means requests do not need special query params for granular route revalidations out of the box - i.e.,
GET /a/b/c.data
- There are two conditions that will trigger granular revalidation:
- If a route opts out of revalidation via
shouldRevalidate
, it will be excluded from the single fetch call
- If a route defines a
clientLoader
then it will be excluded from the single fetch call and if you call serverLoader()
from your clientLoader
, that will make a separarte HTTP call for just that route loader - i.e., GET /a/b/c.data?_routes=routes/a
for a clientLoader
in routes/a.tsx
- When one or more routes are excluded from the single fetch call, the remaining routes that have loaders are included as query params:
- For example, if A was excluded, and the
root
route and routes/b
had a loader
but routes/c
did not, the single fetch request would be GET /a/b/c.data?_routes=root,routes/a
Remove hydration URL check that was originally added for React 17 hydration issues and we no longer support React 17 (#9890)
- Reverts the logic originally added in Remix
v1.18.0
via https://github.com/remix-run/remix/pull/6409
- This was added to resolve an issue that could arise when doing quick back/forward history navigations while JS was loading which would cause a mismatch between the server matches and client matches: https://github.com/remix-run/remix/issues/1757
- This specific hydration issue would then cause this React v17 only looping issue: https://github.com/remix-run/remix/issues/1678
- The URL comparison that we added in
1.18.0
turned out to be subject to false positives of it's own which could also put the user in looping scenarios
- Remix v2 upgraded it's minimal React version to v18 which eliminated the v17 hydration error loop
- React v18 handles this hydration error like any other error and does not result in a loop
- So we can remove our check and thus avoid the false-positive scenarios in which it may also trigger a loop
Single Fetch: Improved typesafety (#9893)
If you were already using previously released unstable single-fetch types:
- Remove
"@remix-run/react/future/single-fetch.d.ts"
override from tsconfig.json
> compilerOptions
> types
- Remove
defineLoader
, defineAction
, defineClientLoader
, defineClientAction
helpers from your route modules
- Replace
UIMatch_SingleFetch
type helper with UIMatch
- Replace
MetaArgs_SingleFetch
type helper with MetaArgs
Then you are ready for the new typesafety setup:
declare module "@remix-run/server-runtime" {
interface Future {
unstable_singleFetch: true;
}
}
export default defineConfig({
plugins: [
remix({
future: {
unstable_singleFetch: true,
},
}),
],
});
For more information, see Guides > Single Fetch in our docs.
Clarify wording in default HydrateFallback
console warning (#9899)
Updated dependencies:
@remix-run/server-runtime@2.12.0
Add unstable support for "SPA Mode" (#8457)
You can opt into SPA Mode by setting unstable_ssr: false
in your Remix Vite plugin config:
import { unstable_vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remix({ unstable_ssr: false })],
});
Development in SPA Mode is just like a normal Remix app, and still uses the Remix dev server for HMR/HDR:
remix vite:dev
Building in SPA Mode will generate an index.html
file in your client assets directory:
remix vite:build
To run your SPA, you serve your client assets directory via an HTTP server:
npx http-server build/client
For more information, please refer to the SPA Mode docs.
Add support for clientLoader
/clientAction
/HydrateFallback
route exports (RFC). (#8173)
Remix now supports loaders/actions that run on the client (in addition to, or instead of the loader/action that runs on the server). While we still recommend server loaders/actions for the majority of your data needs in a Remix app - these provide some levers you can pull for more advanced use-cases such as:
- Leveraging a data source local to the browser (i.e.,
localStorage
)
- Managing a client-side cache of server data (like
IndexedDB
)
- Bypassing the Remix server in a BFF setup and hitting your API directly from the browser
- Migrating a React Router SPA to a Remix application
By default, clientLoader
will not run on hydration, and will only run on subsequent client side navigations.
If you wish to run your client loader on hydration, you can set clientLoader.hydrate=true
to force Remix to execute it on initial page load. Keep in mind that Remix will still SSR your route component so you should ensure that there is no new required data being added by your clientLoader
.
If your clientLoader
needs to run on hydration and adds data you require to render the route component, you can export a HydrateFallback
component that will render during SSR, and then your route component will not render until the clientLoader
has executed on hydration.
clientAction
is simpler than clientLoader
because it has no hydration use-cases. clientAction
will only run on client-side navigations.
For more information, please refer to the clientLoader
and clientAction
documentation.
Add a new future.v3_relativeSplatPath
flag to implement a breaking bug fix to relative routing when inside a splat route. For more information, please see the React Router 6.21.0
Release Notes and the useResolvedPath
docs. (#8216)
Faster server export removal for routes when unstable_dev
is enabled. (#6455)
Also, only render modulepreloads on SSR.
Do not render modulepreloads when hydrated.
Force Typescript to simplify type produced by Serialize
. (#6449)
As a result, the following types and functions have simplified return types:
- SerializeFrom
- useLoaderData
- useActionData
- useFetcher
type Data = { hello: string; when: Date };
type Unsimplified = SerializeFrom<Data>;
type Simplified = SerializeFrom<Data>;
Reuse dev server port for WebSocket (Live Reload,HMR,HDR) (#6476)
As a result the webSocketPort
/--websocket-port
option has been obsoleted.
Additionally, scheme/host/port options for the dev server have been renamed.
Available options are:
| Option | flag | config | default |
| ---------- | ------------------ | ---------------- | --------------------------------- |
| Command | -c
/ --command
| command
| remix-serve <server build path>
|
| Scheme | --scheme
| scheme
| http
|
| Host | --host
| host
| localhost
|
| Port | --port
| port
| Dynamically chosen open port |
| No restart | --no-restart
| restart: false
| restart: true
|
Note that scheme/host/port options are for the dev server, not your app server.
You probably don't need to use scheme/host/port option if you aren't configuring networking (e.g. for Docker or SSL).
Enable support for CSS Modules, Vanilla Extract and CSS side-effect imports (#6046)
These CSS bundling features were previously only available via future.unstable_cssModules
, future.unstable_vanillaExtract
and future.unstable_cssSideEffectImports
options in remix.config.js
, but they have now been stabilized.
In order to use these features, check out our guide to CSS bundling in your project.
Stabilize built-in PostCSS support via the new postcss
option in remix.config.js
. As a result, the future.unstable_postcss
option has also been deprecated. (#5960)
The postcss
option is false
by default, but when set to true
will enable processing of all CSS files using PostCSS if postcss.config.js
is present.
If you followed the original PostCSS setup guide for Remix, you may have a folder structure that looks like this, separating your source files from its processed output:
.
├── app
│ └── styles (processed files)
│ ├── app.css
│ └── routes
│ └── index.css
└── styles (source files)
├── app.css
└── routes
└── index.css
After you've enabled the new postcss
option, you can delete the processed files from app/styles
folder and move your source files from styles
to app/styles
:
.
├── app
│ └── styles (source files)
│ ├── app.css
│ └── routes
│ └── index.css
You should then remove app/styles
from your .gitignore
file since it now contains source files rather than processed output.
You can then update your package.json
scripts to remove any usage of postcss
since Remix handles this automatically. For example, if you had followed the original setup guide:
{
"scripts": {
- "dev:css": "postcss styles --base styles --dir app/styles -w",
- "build:css": "postcss styles --base styles --dir app/styles --env production",
- "dev": "concurrently \"npm run dev:css\" \"remix dev\""
+ "dev": "remix dev"
}
}
Stabilize built-in Tailwind support via the new tailwind
option in remix.config.js
. As a result, the future.unstable_tailwind
option has also been deprecated. (#5960)
The tailwind
option is false
by default, but when set to true
will enable built-in support for Tailwind functions and directives in your CSS files if tailwindcss
is installed.
If you followed the original Tailwind setup guide for Remix and want to make use of this feature, you should first delete the generated app/tailwind.css
.
Then, if you have a styles/tailwind.css
file, you should move it to app/tailwind.css
.
rm app/tailwind.css
mv styles/tailwind.css app/tailwind.css
Otherwise, if you don't already have an app/tailwind.css
file, you should create one with the following contents:
@tailwind base;
@tailwind components;
@tailwind utilities;
You should then remove /app/tailwind.css
from your .gitignore
file since it now contains source code rather than processed output.
You can then update your package.json
scripts to remove any usage of tailwindcss
since Remix handles this automatically. For example, if you had followed the original setup guide:
{
"scripts": {
- "build": "run-s \"build:*\"",
+ "build": "remix build",
- "build:css": "npm run generate:css -- --minify",
- "build:remix": "remix build",
- "dev": "run-p \"dev:*\"",
+ "dev": "remix dev",
- "dev:css": "npm run generate:css -- --watch",
- "dev:remix": "remix dev",
- "generate:css": "npx tailwindcss -o ./app/tailwind.css",
"start": "remix-serve build"
}
}
Deprecated fetcher.type
and fetcher.submission
for Remix v2 (#5691)
We have made a few changes to the API for route module meta
functions when using the future.v2_meta
flag. These changes are only breaking for users who have opted in. (#5746)
V2_HtmlMetaDescriptor
has been renamed to V2_MetaDescriptor
- The
meta
function's arguments have been simplified
parentsData
has been removed, as each route's loader data is available on the data
property of its respective match
object
export function meta({ parentsData }) {
return [{ title: parentsData["routes/some-route"].title }];
}
export function meta({ matches }) {
return [
{
title: matches.find((match) => match.id === "routes/some-route")
.data.title,
},
];
}
- The
route
property on route matches has been removed, as relevant match data is attached directly to the match object
export function meta({ matches }) {
const rootModule = matches.find((match) => match.route.id === "root");
}
export function meta({ matches }) {
const rootModule = matches.find((match) => match.id === "root");
}
- Added support for generating
<script type='application/ld+json' />
and meta-related <link />
tags to document head via the route meta
function when using the v2_meta
future flag
Added deprecation warning for v2_normalizeFormMethod
(#5863)
Added a new future.v2_normalizeFormMethod
flag to normalize the exposed useNavigation().formMethod
as an uppercase HTTP method to align with the previous useTransition
behavior as well as the fetch()
behavior of normalizing to uppercase HTTP methods. (#5815)
- When
future.v2_normalizeFormMethod === false
,
useNavigation().formMethod
is lowercase
useFetcher().formMethod
is uppercase
- When
future.v2_normalizeFormMethod === true
:
useNavigation().formMethod
is uppercase
useFetcher().formMethod
is uppercase
Added deprecation warning for normalizing imagesizes
& imagesrcset
properties returned from the route links
function. Both properties should be in camelCase (imageSizes
/ imageSrcSet
) to align with their respective JavaScript properties. (#5706)
Added deprecation warning for CatchBoundary
in favor of future.v2_errorBoundary
(#5718)
Added experimental support for Vanilla Extract caching, which can be enabled by setting future.unstable_vanillaExtract: { cache: true }
in remix.config
. This is considered experimental due to the use of a brand new Vanilla Extract compiler under the hood. In order to use this feature, you must be using at least v1.10.0
of @vanilla-extract/css
. (#5735)
We enhanced the type signatures of loader
/action
and
useLoaderData
/useActionData
to make it possible to infer the data type
from return type of its related server function.
To enable this feature, you will need to use the LoaderArgs
type from your
Remix runtime package instead of typing the function directly:
- import type { LoaderFunction } from "@remix-run/[runtime]";
+ import type { LoaderArgs } from "@remix-run/[runtime]";
- export const loader: LoaderFunction = async (args) => {
- return json<LoaderData>(data);
- }
+ export async function loader(args: LoaderArgs) {
+ return json(data);
+ }
Then you can infer the loader data by using typeof loader
as the type
variable in useLoaderData
:
- let data = useLoaderData() as LoaderData;
+ let data = useLoaderData<typeof loader>();
The API above is exactly the same for your route action
and useActionData
via the ActionArgs
type.
With this change you no longer need to manually define a LoaderData
type
(huge time and typo saver!), and we serialize all values so that
useLoaderData
can't return types that are impossible over the network, such
as Date
objects or functions.
See the discussions in #1254
and #3276 for more context.
Add WebSocket
reconnect to LiveReload