Releases: remix-run/remix
v1.16.0
Remix is full speed ahead preparing for our upcoming v2 release! 1.16.0
stabilizes a handful of CSS features, contains a major overhaul to the internals of the new remix dev
server. and fixes a bunch of bugs.
Stabilized CSS Features
CSS Modules/Vanilla Extract/CSS Side-Effect Imports
Version 1.16.0 stabilizes built-in support for CSS Modules, Vanilla Extract and CSS side-effect imports, which were previously only available via future flags (future.unstable_cssModules
, future.unstable_vanillaExtract
, future.unstable_cssSideEffectImports
). In order to use these features, check out our guide to CSS bundling in your project.
PostCSS
Version 1.16.0 stabilizes 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. 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. For more information on PostCSS support, check out the docs.
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"
}
}
Tailwind
Version 1.16.0 stabilizes 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. For more information on Tailwind support, check out the docs.
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"
}
}
Dev Server Overhaul
🎥 Livestream of Pedro and Kent going over the new dev server
Version 1.16.0
contains a major overhaul to the new dev server, which can be enabled via the future.unstable_dev
flag in remix.config.js
. The Remix dev server now spins up your app server as a managed subprocess. This keeps your development environment as close to production as possible, and also means that the Remix dev server is compatible with any app server.
By default, the dev server will use the Remix App Server, but you opt to use your own app server by specifying the command to run it via the -c
/--command
flag:
remix dev # uses `remix-serve <serve build path>` as the app server
remix dev -c "node ./server.js" # uses your custom app server at `./server.js`
The dev server will:
- force
NODE_ENV=development
and warn you if it was previously set to something else - rebuild your app whenever your Remix app code changes
- restart your app server whenever rebuilds succeed
- handle live reload and HMR + Hot Data Revalidation
App server coordination
In order to manage your app server, the dev server needs to be told what server build is currently being used by your app server. This works by having the app server send a "I'm ready!" message with the Remix server build hash as the payload.
This is handled automatically in Remix App Server and is set up for you via calls to broadcastDevReady
or logDevReady
in the official Remix templates.
If you are not using Remix App Server and your server doesn't call broadcastDevReady
, you'll need to call it in your app server after it is up and running.
For example, in an Express server:
// server.js
// <other imports>
import { broadcastDevReady } from "@remix-run/node";
// Path to Remix's server build directory ('build/' by default)
const BUILD_DIR = path.join(process.cwd(), "build");
// <code setting up your express server>
app.listen(3000, () => {
const build = require(BUILD_DIR);
console.log("Ready: http://localhost:" + port);
// in development, call `broadcastDevReady` _after_ your server is up and running
if (process.env.NODE_ENV === "development") {
broadcastDevReady(build);
}
});
Cloudflare
For CF workers and pages, you'll need to use logDevReady
instead of broadcastDevReady
as the experimental CF runtime won't play nicely with fetch
which is how broadcastDevReady
is implemented.
For examples, here's a sneak peek 👀 of unstable_dev
-enabled templates. Be sure to check out how logDevReady
is called in the server.ts
files for CF workers and for CF pages.
Options
Options priority order is: 1. flags, 2. config, 3. defaults.
Option | flag | config | default |
---|---|---|---|
Command | -c / --command |
command |
remix-serve <server build path> |
HTTP(S) scheme | --http-scheme |
httpScheme |
http |
HTTP(S) host | --http-host |
httpHost |
localhost |
HTTP(S) port | --http-port |
httpPort |
Dynamically chosen open port |
Websocket port | --websocket-port |
websocketPort |
Dynamically chosen open port |
No restart | --no-restart |
restart: false |
restart: true |
🚨 The --http-*
flags are only used for internal dev server <-> app server communication.
Your app will run on your app server's normal URL.
To set unstable_dev
configuration, replace unstable_dev: true
with unstable_dev: { <options> }
.
For example, to set the HTTP(S) port statically:
// remix.config.js
module.exports = {
future: {
unstable_dev: {
httpPort: 8001,
},
},
};
SSL and custom hosts
You should only need to use the --http-*
flags and --websocket-port
flag if you need fine-grain control of what scheme/host/port for the dev server. If you are setting up SSL or Docker networking, these are the flags you'll want to use.
🚨 Remix will not set up SSL and custom host for you.
The --http-scheme
and --http-host
flag are for you to tell Remix how you've set things up. It is your task to set up SSL certificates and host files if you want those features.
--no-restart
and require
cache purging
If you want to manage server changes yourself, you can use the --no-restart
flag to tell the dev server to refrain from restarting your app server when builds succeed:
remix dev -c "node ./server.js" --no-restart
For example, you could purge the require
cache of your app server to keep it running while picking up server changes. If you do so, you should watch the server build path (build/
by default) for changes and only purge the require
cache when changes are detected.
🚨 If you use --no-restart
, it is your responsibility to call broadcastDevReady
when your app server has picked up server changes.
For example, with chokidar
:
// server.dev.js
const BUILD_PATH = path.resolve(__dirname, "build");
const watcher = chokidar.watch(BUILD_PATH);
watcher.on("all", () => {
// 1...
v1.15.0
For the last few months we've been working hard on bringing new and improved APIs to Remix. We've introduced several major changes to prepare you for v2 via future
flags, and we think v1.15.0 is our last big push to get you ready for the future of Remix.
It's important to note that nothing in your app will break if you do not opt-in to our future flags. 🥳 We are on a mission to provide the smoothest possible upgrade path so you can take it on before a new major release.
Let's get into it 🤓
Preparing for v2 Guide
While these release notes have some information about the APIs, the best way to prepare for v2 is to read the new guide in the docs:
Future Flags
As of v1.15.0, we have deprecated all v1 APIs impacted by v2 future
flags. When you run your app in development, we will show a one-time warning for deprecated APIs along with a link that explains how to incrementally migrate to the new APIs before v2 is released.
The v2 future
flags include:
v2_errorBoundary
: Removes theCatchBoundary
component in favor of handling thrownResponse
objects inErrorBoundary
directlyv2_meta
: Uses the new function signature for routemeta
functions and simplifies rendering in<Meta />
v2_routeConvention
: Uses new "flat route" naming conventionsv2_normalizeFormMethod
:useNavigation
anduseFetcher
hooks returning an object withformMethod
will uses uppercase method verbs ("GET"
,"POST"
, etc.) to align withfetch()
behavior
For detailed information on how to use these flags and incrementally upgrade your app, please refer to Preparing for v2 in the Remix docs.
Changes to v2 meta
We have made a few changes to the API for route module meta
functions when using the future.v2_meta
flag.
V2_HtmlMetaDescriptor
has been renamed toV2_MetaDescriptor
- The
meta
function's arguments have been simplifiedparentsData
has been removed, as each route's loader data is available on thedata
property of its respectivematch
object// before export function meta({ parentsData }) { return [{ title: parentsData["routes/parent"].title }]; } // after export function meta({ matches }) { let parent = matches.find((match) => match.id === "routes/parent"); return [{ title: parent.data.title }]; }
- The
route
property on route matches has been removed, as relevant match data is attached directly to the match object// before export function meta({ matches }) { let rootModule = matches.find((match) => match.route.id === "root"); } // after export function meta({ matches }) { let rootModule = matches.find((match) => match.id === "root"); }
- We have added support for generating
<script type='application/ld+json' />
and meta-related<link />
tags to document head via the routemeta
function
See the v2 meta
docs for more information.
useTransition
becomes useNavigation
We have deprecated the useTransition
hook in favor of the new useNavigation
. When we were designing early versions of Remix, the React team shipped their own [at the time] experimental hook called useTransition
. To avoid confusion we plan to remove our useTransition
hook in v2.
The signature for useNavigation
is similar but makes a few key changes to note when upgrading:
import { useTransition } from "@remix-run/react";
function SomeComponent() {
// before
let {
location,
state,
submission: { formAction, formData, formMethod },
type,
} = useTransition();
// after
let {
// `submission` properties have been flattened
formAction,
formData,
formMethod,
location,
state,
} = useNavigation();
}
Notably, the type
property has been removed. It can be derived from the state
of a navigation alongside its other properties. See the v2 upgrade guide in our docs for more details.
Deprecated remix.config
options
browserBuildDirectory
has been deprecated in favor ofassetsBuildDirectory
serverBuildDirectory
has been deprecated in favor ofserverBuildPath
serverBuildTarget
has been deprecated in favor of more granular configuration to build for a wider variety of environments
Other notable changes
- The
V2_HtmlMetaDescriptor
type has been renamed toV2_MetaDescriptor
- We now ensure that stack traces are removed from all server side errors in production
entry.client
andentry.server
files are now optional, making it simpler to start a new Remix app without a template or boilerplate- Bumped React Router dependencies to the latest version. See the release notes for more details.
- Fixed issue to ensure changes to CSS inserted via
@remix-run/css-bundle
are picked up during HMR - Added experimental support for Vanilla Extract caching, which can be enabled by setting
future.unstable_vanillaExtract: { cache: true }
inremix.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 leastv1.10.0
of@vanilla-extract/css
.
Changes by Package 🔗
@remix-run/cloudflare
@remix-run/css-bundle
@remix-run/deno
@remix-run/dev
@remix-run/eslint-config
@remix-run/node
@remix-run/react
@remix-run/server-runtime
@remix-run/testing
Full Changelog: 1.14.3...1.15.0
v1.14.3
Fix regression #5631 : dev server keeps running when browser or server builds fail (#5795)
- Changes by Package 🔗
Full Changelog: 1.14.2...1.14.3
v1.14.2
1.14.2
doesn't contain any functional changes. It just removes a few deprecation warnings that inadvertently shipped in 1.14.1
but were intended for the upcoming 1.15.0
release.
Full Changelog: 1.14.1...1.14.2
v1.14.1
Just a bit of housekeeping and a few bug-fixes in this release! Check out the release notes for each package to see what we're up to 👀
- Changes by Package 🔗
New Contributors
- @wizardlyhel made their first contribution in #5603
- @buzinas made their first contribution in #5576
- @TomerAberbach made their first contribution in #5430
Full Changelog: 1.14.0...1.14.1
v1.14.0
New Development Server with HMR 🔥
You asked for it, and now we're stoked to deliver. We've got a brand new dev server that we think will dramatically improve the experience of running your Remix apps in development.
The new dev environment includes long-anticipated Hot Module Replacement (HMR) via React Refresh, as well as something we're calling Hot Data Revalidation (HDR)
HMR allows you to make changes to your UI or style code and see them reflected in your browser without having to refresh the page. This is different from our existing <LiveReload>
component, as HMR will not reset client-side state between updates. This is particularly useful for highly-interactive apps where resetting state is disruptive and slows down the development process.
Now for HDR. Think of it as HMR for data loaders. With HDR you can make changes to your server code and see those updates reflected in your UI immediately without resetting client-side state, just like HMR.
This is an early release available under the unstable_dev
future flag, but we're excited to get it into your hands, gather feedback and provide a first-class developer experience for apps at any scale. As of now, there are some known limitations to be aware of:
- We don't yet expose an API for
import.meta.hot
- All route loaders are invalidated when changes are detected on the server
- Loader changes do not account for changes in imported dependencies
- It's doesn't work automatically with the Remix App Server, you'll want to bring in
@remix-run/express
for your server. This will not be a limitation when the unstable flag is removed.
Using HMR/HDR
First, you need to enable the new dev server in your remix.config.js
:
module.exports = {
// ...
future: {
unstable_dev: true,
},
};
The new dev server and HMR/HDR requires two processes, one for your build and one for your app. You can run these in separate tabs or you can use something like npm-run-all
to run them in parallel via a package.json
script. We are also using nodemon
to auto-restart our server on build changes. It's important to note that we're setting NODE_ENV=development
here which is required to enable HMR/HDR.
Using the Remix App Server:
// package.json scripts
"dev": "run-p dev:*",
"dev:build": "cross-env NODE_ENV=development remix dev",
"dev:serve": "cross-env NODE_ENV=development nodemon --watch build node_modules/.bin/remix-serve build",
Using an Express Server:
// package.json scripts
"dev": "run-p dev:*",
"dev:build": "cross-env NODE_ENV=development remix dev",
"dev:serve": "cross-env NODE_ENV=development nodemon --watch build server.js",
Other notable changes
entry.server
andentry.client
files are now optional. If excluded, Remix will use reasonable defaults at build-time. If you need customization for these files, you can runnpx remix reveal
and it will generate them for you.- For users using the
v2_routeConvention
flag, route conflicts will no longer throw errors. Instead, you'll see a helpful warning that points to the conflict, and the first match we find will be used.⚠️ Route Path Collision: "/dashboard" The following routes all define the same URL, only the first one will be used 🟢️️ routes/dashboard/route.tsx ⭕️️ routes/dashboard.tsx
⚠️ Route Path Collision: "/" The following routes all define the same URL, only the first one will be used 🟢️️ routes/_landing._index.tsx ⭕️️ routes/_dashboard._index.tsx ⭕️ routes/_index.tsx
Miscellaneous
- Updated to latest React Router versions. See the release notes for more details.
[email protected]
@remix-run/[email protected]
- Changes by Package 🔗
v1.13.0
This Valentine's Day, get that special someone in your life what they really want: more styling options—out of the box—in Remix 🥰
Built-in PostCSS support
Remix can now process your existing CSS imports with PostCSS. A lot of folks have been doing this for quite some time, but in Remix this previously required you to run any CSS transformations as a separate process, and imports would need to reference the output rather than the source.
No longer! Now you can import references to the CSS files you actually write and Remix will take care of the rest. This is opt-in under the future.unstable_postcss
flag in remix.config
. From there, all you need is PostCSS configured in your app.
// remix.config.js
module.exports = {
future: {
unstable_postcss: true,
},
};
// postcss.config.js
module.exports = {
plugins: [/* your plugins here! */],
presets: [/* your presets here! */],
};
// app/routes/root.jsx
// huzzah, the stylez are transformed before your very eyes!
import stylesheet from "./root.css";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
];
Built-in Tailwind support
Valentine's Day is all about expressing how you feel, and what else evokes stronger feelings than Tailwind? 🙈
For all of our fellow lovers, you can now get your Tailwind styles generated without running a separate process. As with PostCSS, you'll need to opt-in (for now) with the future.unstable_tailwind
flag.
// remix.config.js
module.exports = {
future: {
unstable_tailwind: true,
},
};
If you haven't already, install Tailwind and initialize a config file.
npm install -D tailwindcss
npx tailwindcss init
Then you can create a stylesheet and use Tailwind directives wherever you'd like,
/* app/tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Now you can import your stylesheet and link to it in your route, and you're good to go!
// app/routes/root.jsx
// huzzah, the stylez are transformed before your very eyes!
import stylesheet from "~/tailwind.css";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
];
Fine-tuning your server build
We are deprecating serverBuildTarget
in remix.config
. Instead, you can target your server build with our more granular config options, granting you much more flexibility in where and how you ship.
Moving forward, you have a number of options that will help you configure your server bundle to fit your needs:
publicPath
serverBuildPath
serverConditions
serverDependenciesToBundle
serverMainFields
serverMinify
serverModuleFormat
serverPlatform
Fixes and enhancements for v2_routeConventions
We recently rolled out early experimental support for new route conventions to prepare you for Remix v2. We've fixed a few bugs and made a number of improvements for our eager early adopters.
One such enhancement we wanted to call out is how we can disambiguate "index routes" from "index modules". Turns out many people don't know about "index modules" or "folder imports" in node.js that our "flat route folders" depended on. Instead of trying to explain the difference, we went with a new convention: route.tsx
.
When you want to split your route module out into multiple co-located modules, you can turn it into a folder and put the route at folder/route.tsx
(or folder/route.jsx
) and the rest of the modules in the folder will be ignored.
Consider this route:
routes/contact.tsx
When you want to split it up, you can make it a folder with a route.tsx
file inside:
routes/contact/route.tsx <- this is the route module
routes/contact/form.tsx <- this is just a module, ignored by the route convention
For those of you who do know the difference between an index route and a node index module, index.tsx
will continue to work inside folders, but we'd rather you use route.tsx
For more information, check out the docs for the v2 file convention.
Please note that this only applies if you have opted in to the new route conventions in future.v2_routeConventions
. Current v1 behavior for file-system routing has not changed.
v1.12.0
We've got a few nice bug fixes and improvements in this release. You can catch those in the individual package changelogs below, but what we really want to talk about today is a huge step forward in a much nicer developer experience for you. Let's get to it!
An early peek at a new dev server 👀
Since the early days of Remix, the remix dev
command has been limited by its association with the built-in Remix app server while remix watch
simply watched your files and spit out builds, but doesn't handle requests like some other tools. But one of the major benefits of this is that Remix lets you own your server. While this is a very powerful core feature of our architecture, it presented a few challenges for local development. If you own your server, how can we build a dev server that is flexible enough to work for your specific setup?
Our first answer was … we don't. Don't try to be clever, don't over-abstract anything. Simply run the remix watch
, spit out files, and have <LiveReload>
tell the browser to reload. But we didn't know if your app server was done rebooting the server code! And in the case of remix dev
with remix-serve
, we didn't reboot the server, we simply wiped out the node require cache as a budget server side HMR.
There were a few downsides for local development with this:
- Clearing the require cache wipes in-memory references, such as database connections or in-memory caches. This means you have to use workaround like sticking flags on
global
to persist anything stored in memory across rebuilds. - Getting the web socket port to
remix watch
and over to your app server in a different process (whether it was express, netlify def, vercel dev, etc.) and using it inLiveReload
was a lot of unnecessary friction. - It makes implementing popular new dev features like hot-module replacement (HMR) much more difficult to implement in a way that works consistently across all app servers.
- Each rebuild writes new files to
build/
andpublic/build/
. As these files are not removed (unless the dev server crashes or is gracefully terminated), thousands of files could accumulate as the dev server ran. This causes performance issues and could be confusing when debugging.
To start addressing these issues, we decided to take a new approach. This release gives you early access to our new dev server and start making incremental improvements to your local development flow.
Please note that this is an unstable feature and a work-in-progress. But we think there are good reasons to opt in right away!
At this stage, the idea is to spin up the dev server alongside your normal Remix app server:
# spin up the new dev server
remix dev
# Spin up your app server in parallel.
# This can be done in a separate terminal or with a tool like `concurrently`.
nodemon --watch build/ ./server.js
Our dev server will build your app in development mode and then rebuild whenever any app files changes. It will also wait for your app server to signal that it's "ready" before triggering a reload in your browser.
No more wiping in-memory caches. No more weird hacks to keep your database alive. A fresh, clean slate for each rebuild.
While this may seem like a relatively small change, but it's removes a huge barrier for future DX improvements for local development. We think you're going to love what we're working on next 🔥🔁
Limitations
The new dev server does not currently work with Remix App Server (i.e. remix-serve
command) because Remix App Server hardcodes NODE_ENV=production
.
Rest assured, when the new dev server stabilizes it will support Remix App Server as well.
Configuring the unstable_dev
server
To enable the new dev server with all defaults, set the unstable_dev
future flag to true
:
// remix.config.js
module.exports = {
future: {
unstable_dev: true,
},
};
You can also set specific options. As this is an unstable feature, these options may change before the next major release.
// remix.config.js
module.exports = {
future: {
unstable_dev: {
// Port to use for the dev server (i.e. the <LiveReload> websocket)
// This can be overridden by a CLI flag: `remix dev --port 3011`
// By default, we will find an empty port and use that
port: 3010,
// Port for running your Remix app server
// This can be overridden by a CLI flag: `remix dev --app-server-port 3021`
// default: `3000`
appServerPort: 3020,
// Path to the Remix request handler in your app server
// Most app servers will route all requests to the Remix request
// handler and will not need this option. If your app server _does_
// route only certain request paths to the Remix request handler, then
// you'll need to set this.
// default: `""`
remixRequestHandlerPath: "/products",
// The number of milliseconds between "readiness" pings to your app server
// When a Remix rebuild finishes, the dev server will ping a special
// endpoint (`__REMIX_ASSETS_MANIFEST`) to check if your app server is
// serving up-to-date routes and assets. You can set this option to tune
// how frequently the dev server polls your app server.
// default: `50`
rebuildPollIntervalMs: 25,
},
},
};
Other Stuff
- Updated to latest
react-router
versions. See the release notes for more details.[email protected]
@remix-run/[email protected]
- You can now configure the client-side socket timeout via the new
timeoutMs
prop on<LiveReload />
(#4036) <Link to>
can now accept absolute URLs. When theto
value is an absolute URL, the underlying anchor element will behave as normal, and its URL will not be prefetched. (#5092)- Added support for
unstable_useBlocker
andunstable_usePrompt
from React Router (#5151) - Removed
react
&react-dom
frompeerDependencies
(#4801)
Changes by Package 🔗
v1.11.1
Fixed a bug with v2_routeConvention
that prevented index
files from being recognized as route modules
v1.11.0
New features.
That's it. That's 1.11.0 in a nutshell.
We're dropping a serious feature 💣 on you this week, so strap yourself in.
Promises over the wire with defer
Today we bring you one of our favorite features from React Router 6.4.
Remix aims to provide first-class support for React 18's SSR streaming capabilities. We can do that today with defer
.
When you return a defer
function call in a route loader, you are initiating a streamed response. This is very useful in cases where lower priority data may take a bit more time. You don't want your users to wait on the slow data if the important stuff is ready to roll.
import { json } from "@remix-run/node";
export async function loader({ request }) {
let [productInfo, productReviews] = await Promise.all([
// Product info query is small, cached aggressively,
// and high priority for the user.
getProductInfo(request),
// Product reviews query is large and cache is more lax.
// It also appears at the bottom of the page and is a lower
// priority, so the user probably doesn't need it right away.
getProductReviews(request),
]);
// Without streaming, we gotta wait for both queries to resolve
// before we can send a response. Our user is getting impatient.
// They probably found something on your competitor's site that
// loaded in the mean time. Wave goodbye to that new yacht you
// were planning to buy with the earnings!
return json({ productInfo, productReviews });
}
In these cases, the slower data is passed to defer
as a promise, and everything else a resolved value.
import { defer } from "@remix-run/node";
export async function loader({ request }) {
// Product info query is small, cached aggressively, and
// high priority for the user. Let's go ahead and let it
// resolve since it's fast!
let productInfo = await getProductInfo(request);
// Product reviews query is large and cache is more lax.
// Let's initiate the query but not wait for it to resolve.
let productReviewsPromise = getProductReviews(request);
// With defer, we initate a streaming response. This allows
// the user to access the resolved data (`productInfo`) as
// soon as it's available, while the unresolved product
// reviews are loaded in the background.
// Enjoy the yacht, call us from Cabo!
return defer({
productInfo,
productReviewsPromise,
});
}
Now you may be thinking, this sounds cool … but what the heck do I do with a promise in my UI while my user waits for reviews? That's where <Await>
comes in, with a little help from React Suspense.
import { Await } from "@remix-run/react";
function ProductRoute() {
let {
// Product info has already resolved. Render immediately!
productInfo,
// Product reviews might not be ready yet 🤔
productReviewsPromise,
} = useLoaderData();
return (
<div>
<h1>{productInfo.name}</h1>
<p>{productInfo.description}</p>
<BuyNowButton productId={productInfo.id} />
<hr />
<h2>Reviews</h2>
<React.Suspense fallback={<p>Loading reviews...</p>}>
<Await resolve={productReviewsPromise} errorElement={<ReviewsError />}>
{(productReviews) =>
productReviews.map((review) => (
<div key={review.id}>
<h3>{review.title}</h3>
<p>{review.body}</p>
</div>
))
}
</Await>
</React.Suspense>
</div>
);
}
// Error fetching the data? Slow connection timed out?
// Show an error message *only* for reviews. The rest
// of your product UI is still usable!
function ReviewsError() {
let error = useAsyncError(); // Get the rejected value
return <p>There was an error loading reviews: {error.message}</p>;
}
Documentation for the new feature can be found at the following links:
- https://remix.run/docs/en/v1/guides/streaming
- https://remix.run/docs/en/v1/components/await
- https://remix.run/docs/en/v1/utils/defer
Built-in support for bundling CSS
Many common approaches to CSS within the React community are only possible when bundling CSS, meaning that the CSS files you write during development are collected into a separate bundle as part of the build process.
Remix has always left stylesheets up to you. All we cared about was having a static stylesheet to work with, but some CSS tools require deeper bundler integration to implement.
With this release, we can now support:
- Direct CSS side-effect imports
- CSS Modules
- Vanilla Extract
Unlike many other tools in the React ecosystem, we do not insert the CSS bundle into the page automatically. Instead, we ensure that you always have control over the link tags on your page. This lets you decide where the CSS file is loaded relative to other stylesheets in your app.
To get started, first install the new @remix-run/css-bundle
package:
npm install @remix-run/css-bundle
Then, in your root route file, import cssBundleHref
and include it in your links
export, the same as you would any other stylesheet. This will load your bundled CSS in your entire app (though the same method could be used at any level in the route tree if you'd like!)
import { cssBundleHref } from "@remix-run/css-bundle";
import resetsStylesheetHref from "./resets.css";
import overridesStylesheetHref from "./overrides.css";
export function links() {
return [
{ rel: "stylesheet", href: resetsStylesheetHref },
{ rel: "stylesheet", href: cssBundleHref },
{ rel: "stylesheet", href: overridesStylesheetHref },
];
}
Please note that these features are currently flagged as unstable
. We're confident in the tools themselves, but the API and implementation may change in the future before a major release.
All three CSS bundling options are opt-in via the future
key in remix.config.js
:
module.exports = {
future: {
unstable_cssModules: true,
unstable_vanillaExtract: true,
unstable_cssSideEffectImports: true,
},
};
For more details on each approach, check out our styling docs.
New route conventions for Remix v2
In the next major version of Remix, we will introduce a new default for how our routing conventions work. You can get a head start on upgrading your app by enabling the v2_routeConvention
future flag in your Remix config.
The new convention allows for a flat directory structure for your routes. For apps with deeply nested routes, this can be a huge productivity boost as you no longer need to jump through several layers of folders to find what you're looking for.
But what if I like having a bunch of folders for my routes?
That's great! You can keep doing that today with no changes! 🥳
When we ship v2, you'll simply need to use the routes
option in your Remix config to define the old route conventions. We'll be updating the docs and provide a helper function to make it easier for you to migrate without moving files around if that's what you prefer.
In the mean time, check out the RFC for the new routing convention to get a head start on things to come. We'll update the release notes as soon as the docs are polished ✨