Handle custom envDir
in Vite config (#12969)
Fix typegen for repeated params (#13012)
In React Router, path parameters are keyed by their name.
So for a path pattern like /a/:id/b/:id?/c/:id
, the last :id
will set the value for id
in useParams
and the params
prop.
For example, /a/1/b/2/c/3
will result in the value { id: 3 }
at runtime.
Previously, generated types for params incorrectly modeled repeated params with an array.
So /a/1/b/2/c/3
generated a type like { id: [1,2,3] }
.
To be consistent with runtime behavior, the generated types now correctly model the "last one wins" semantics of path parameters.
So /a/1/b/2/c/3
now generates a type like { id: 3 }
.
Fix CLI parsing to allow argumentless npx react-router
usage (#12925)
Fix ArgError: unknown or unexpected option: --version
when running react-router --version
(#13012)
Skip action-only resource routes when using prerender:true
(#13004)
Enhance invalid export detection when using ssr:false
(#12948)
headers
/action
are prohibited in all routes with ssr:false
because there will be no runtime server on which to run them
loader
functions are more nuanced and depend on whether a given route is prerendered
- When using
ssr:false
without a prerender
config, only the root
route can have a loader
- This is "SPA mode" which generates a single
index.html
file with the root route HydrateFallback
so it is capable of hydrating for any path in your application - therefore we can only call a root route loader
at build time
- When using
ssr:false
with a prerender
config, you can export a loader
from routes matched by one of the prerender
paths because those routes will be server rendered at build time
- Exporting a
loader
from a route that is never matched by a prerender
path will throw a build time error because there will be no runtime server to ever run the loader
Limit prerendered resource route .data
files to only the target route (#13004)
Add unstable support for splitting route modules in framework mode via future.unstable_splitRouteModules
(#11871)
Fix prerendering of binary files (#13039)
Add future.unstable_viteEnvironmentApi
flag to enable experimental Vite Environment API support (#12936)
Disable Lazy Route Discovery for all ssr:false
apps and not just "SPA Mode" because there is no runtime server to serve the search-param-configured __manifest
requests (#12894)
- We previously only disabled this for "SPA Mode" which is
ssr:false
and no prerender
config but we realized it should apply to all ssr:false
apps, including those prerendering multiple pages
- In those
prerender
scenarios we would prerender the /__manifest
file assuming the static file server would serve it but that makes some unneccesary assumptions about the static file server behaviors
Updated dependencies:
For Remix consumers migrating to React Router, the vitePlugin
and cloudflareDevProxyVitePlugin
exports have been renamed and moved. (#11904)
-import {
- vitePlugin as remix,
- cloudflareDevProxyVitePlugin,
-} from "@remix/dev";
+import { reactRouter } from "@react-router/dev/vite";
+import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
Remove single fetch future flag. (#11522)
update minimum node version to 18 (#11690)
Add exports
field to all packages (#11675)
node package no longer re-exports from react-router (#11702)
For Remix consumers migrating to React Router who used the Vite plugin's buildEnd
hook, the resolved reactRouterConfig
object no longer contains a publicPath
property since this belongs to Vite, not React Router. (#11575)
For Remix consumers migrating to React Router, the Vite plugin's manifest
option has been removed. (#11573)
The manifest
option been superseded by the more powerful buildEnd
hook since it's passed the buildManifest
argument. You can still write the build manifest to disk if needed, but you'll most likely find it more convenient to write any logic depending on the build manifest within the buildEnd
hook itself.
If you were using the manifest
option, you can replace it with a buildEnd
hook that writes the manifest to disk like this:
import type { Config } from "@react-router/dev/config";
import { writeFile } from "node:fs/promises";
export default {
async buildEnd({ buildManifest }) {
await writeFile(
"build/manifest.json",
JSON.stringify(buildManifest, null, 2),
"utf-8"
);
},
} satisfies Config;
Consolidate types previously duplicated across @remix-run/router
, @remix-run/server-runtime
, and @remix-run/react
now that they all live in react-router
(#12177)
- Examples:
LoaderFunction
, LoaderFunctionArgs
, ActionFunction
, ActionFunctionArgs
, DataFunctionArgs
, RouteManifest
, LinksFunction
, Route
, EntryRoute
- The
RouteManifest
type used by the "remix" code is now slightly stricter because it is using the former @remix-run/router
RouteManifest
Record<string, Route> -> Record<string, Route | undefined>
- Removed
AppData
type in favor of inlining unknown
in the few locations it was used
- Removed
ServerRuntimeMeta*
types in favor of the Meta*
types they were duplicated from
Update default isbot
version to v5 and drop support for isbot@3
(#11770)
- If you have
isbot@4
or isbot@5
in your package.json
:
- You do not need to make any changes
- If you have
isbot@3
in your package.json
and you have your own entry.server.tsx
file in your repo
- You do not need to make any changes
- You can upgrade to
isbot@5
independent of the React Router v7 upgrade
- If you have
isbot@3
in your package.json
and you do not have your own entry.server.tsx
file in your repo
- You are using the internal default entry provided by React Router v7 and you will need to upgrade to
isbot@5
in your package.json
Drop support for Node 18, update minimum Node vestion to 20 (#12171)
- Remove
installGlobals()
as this should no longer be necessary
For Remix consumers migrating to React Router, Vite manifests (i.e. .vite/manifest.json
) are now written within each build subdirectory, e.g. build/client/.vite/manifest.json
and build/server/.vite/manifest.json
instead of build/.vite/client-manifest.json
and build/.vite/server-manifest.json
. This means that the build output is now much closer to what you'd expect from a typical Vite project. (#11573)
Originally the Remix Vite plugin moved all Vite manifests to a root-level build/.vite
directory to avoid accidentally serving them in production, particularly from the client build. This was later improved with additional logic that deleted these Vite manifest files at the end of the build process unless Vite's build.manifest
had been enabled within the app's Vite config. This greatly reduced the risk of accidentally serving the Vite manifests in production since they're only present when explicitly asked for. As a result, we can now assume that consumers will know that they need to manage these additional files themselves, and React Router can safely generate a more standard Vite build output.
Allow an optional Layout
export from the root route (#8709)
Vite: Cloudflare Proxy as a Vite plugin (#8749)
This is a breaking change for projects relying on Cloudflare support from the unstable Vite plugin
The Cloudflare preset (unstable_cloudflarePreset
) as been removed and replaced with a new Vite plugin:
import {
unstable_vitePlugin as remix,
- unstable_cloudflarePreset as cloudflare,
+ cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,
} from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
+ remixCloudflareDevProxy(),
+ remix(),
- remix({
- presets: [cloudflare()],
- }),
],
- ssr: {
- resolve: {
- externalConditions: ["workerd", "worker"],
- },
- },
});
remixCloudflareDevProxy
must come before the remix
plugin so that it can override Vite's dev server middleware to be compatible with Cloudflare's proxied environment.
Because it is a Vite plugin, remixCloudflareDevProxy
can set ssr.resolve.externalConditions
to be workerd
-compatible for you.
remixCloudflareDevProxy
accepts a getLoadContext
function that replaces the old getRemixDevLoadContext
.
If you were using a nightly
version that required getBindingsProxy
or getPlatformProxy
, that is no longer required.
Any options you were passing to getBindingsProxy
or getPlatformProxy
should now be passed to remixCloudflareDevProxy
instead.
This API also better aligns with future plans to support Cloudflare with a framework-agnostic Vite plugin that makes use of Vite's (experimental) Runtime API.
Vite: Stabilize the Remix Vite plugin, Cloudflare preset, and all related types by removing all unstable_
/ Unstable_
prefixes. (#8713)
While this is a breaking change for existing Remix Vite plugin consumers, now that the plugin has stabilized, there will no longer be any breaking changes outside of a major release. Thank you to all of our early adopters and community contributors for helping us get here! 🙏
Vite: Stabilize "SPA Mode" by renaming the Remix vite plugin config from unstable_ssr -> ssr
(#8692)
Vite: Add a new basename
option to the Vite plugin, allowing users to set the internal React Router basename
in order to to serve their applications underneath a subpath (#8145)
Vite: fix server exports dead-code elimination for routes outside of app directory (#8795)
Always prepend DOCTYPE in SPA mode entry.server.tsx, can opt out via remix reveal (#8725)
Fix build issue in SPA mode when using a basename
(#8720)
Vite: Validate that the MDX Rollup plugin, if present, is placed before Remix in Vite config (#8690)
Vite: reliably detect non-root routes in Windows (#8806)
Sometimes route file
will be unnormalized Windows path with \
instead of /
.
Vite: Pass remixUserConfig
to preset remixConfig
hook (#8797)
Vite: Fix issue resolving critical CSS during development when the current working directory differs from the project root (#8752)
Vite: Ensure CSS file URLs that are only referenced in the server build are available on the client (#8796)
Vite: Require version 5.1.0 to support .css?url
imports (#8723)
Fix type error in Remix config for synchronous routes
function (#8745)
Vite: Support Vite v5.1.0's .css?url
imports (#8684)
Always ignore route files starting with .
(#8801)
Vite: Enable use of vite preview
to preview Remix SPA applications (#8624)
- In the SPA template,
npm run start
has been renamed to npm run preview
which uses vite preview
instead of a standalone HTTP server such as http-server
or serv-cli
Vite: Remove the ability to pass publicPath
as an option to the Remix vite plugin (#8145)
- ⚠️ This is a breaking change for projects using the unstable Vite plugin with a
publicPath
- This is already handled in Vite via the
base
config so we now set the Remix publicPath
from the Vite base
config
Vite: Fix issue where client route file requests fail if search params have been parsed and serialized before reaching the Remix Vite plugin (#8740)
Vite: Enable HMR for .md and .mdx files (#8711)
Updated dependencies:
@remix-run/server-runtime@2.7.0
@remix-run/node@2.7.0
Vite: Add manifest
option to Vite plugin to enable writing a .remix/manifest.json
file to the build directory (#8575)
This is a breaking change for consumers of the Vite plugin's "server bundles" feature.
The build/server/bundles.json
file has been superseded by the more general build/.remix/manifest.json
. While the old server bundles manifest was always written to disk when generating server bundles, the build manifest file must be explicitly enabled via the manifest
option.
Vite: Provide Unstable_ServerBundlesFunction
and Unstable_VitePluginConfig
types (#8654)
Vite: add --sourcemapClient
and --sourcemapServer
flags to remix vite:build
(#8613)
--sourcemapClient
--sourcemapClient=inline
--sourcemapClient=hidden
--sourcemapServer
--sourcemapServer=inline
--sourcemapServer=hidden
See https://vitejs.dev/config/build-options.html#build-sourcemap
Vite: Validate IDs returned from the serverBundles
function to ensure they only contain alphanumeric characters, hyphens and underscores (#8598)
Vite: fix "could not fast refresh" false alarm (#8580)
HMR is already functioning correctly but was incorrectly logging that it "could not fast refresh" on internal client routes.
Now internal client routes correctly register Remix exports like meta
for fast refresh,
which removes the false alarm.
Vite: Cloudflare Pages support (#8531)
To get started with Cloudflare, you can use the [unstable-vite-cloudflare
][template-vite-cloudflare] template:
shellscript nonumber
npx create-remix@latest --template remix-run/remix/templates/unstable-vite-cloudflare
Or read the new docs at Future > Vite > Cloudflare and
Future > Vite > Migrating > Migrating Cloudflare Functions.
Vite: Remove undocumented backwards compatibility layer for Vite v4 (#8581)
Vite: rely on Vite plugin ordering (#8627)
This is a breaking change for projects using the unstable Vite plugin.
The Remix plugin expects to process JavaScript or TypeScript files, so any transpilation from other languages must be done first.
For example, that means putting the MDX plugin before the Remix plugin:
import mdx from "@mdx-js/rollup";
import { unstable_vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
+ mdx(),
remix()
- mdx(),
],
});
Previously, the Remix plugin misused enforce: "post"
from Vite's plugin API to ensure that it ran last.
However, this caused other unforeseen issues.
Instead, we now rely on standard Vite semantics for plugin ordering.
The official Vite React SWC plugin also relies on plugin ordering for MDX.
Vite: Add presets
option to ease integration with different platforms and tools. (#8514)
Vite: Remove interop with <LiveReload />
, rely on <Scripts />
instead (#8636)
This is a breaking change for projects using the unstable Vite plugin.
Vite provides a robust client-side runtime for development features like HMR,
making the <LiveReload />
component obsolete.
In fact, having a separate dev scripts component was causing issues with script execution order.
To work around this, the Remix Vite plugin used to override <LiveReload />
into a bespoke
implementation that was compatible with Vite.
Instead of all this indirection, now the Remix Vite plugin instructs the <Scripts />
component
to automatically include Vite's client-side runtime and other dev-only scripts.
import {
- LiveReload,
Outlet,
Scripts,
}
export default function App() {
return (
<html>
<head>
</head>
<body>
<Outlet />
<Scripts />
- <LiveReload />
</body>
</html>
)
}
Vite: Add buildEnd
hook (#8620)
Vite: add dev load context option to Cloudflare preset (#8649)
Vite: Add mode
field into generated server build (#8539)
Vite: Only write Vite manifest files if build.manifest
is enabled within the Vite config (#8599)
This is a breaking change for consumers of Vite's manifest.json
files.
To explicitly enable generation of Vite manifest files, you must set build.manifest
to true
in your Vite config.
export default defineConfig({
build: { manifest: true },
});
Vite: reduce network calls for route modules during HMR (#8591)
Vite: Add new buildDirectory
option with a default value of "build"
. This replaces the old assetsBuildDirectory
and serverBuildDirectory
options which defaulted to "build/client"
and "build/server"
respectively. (#8575)
This is a breaking change for consumers of the Vite plugin that were using the assetsBuildDirectory
and serverBuildDirectory
options.
The Remix Vite plugin now builds into a single directory containing client
and server
directories. If you've customized your build output directories, you'll need to migrate to the new buildDirectory
option, e.g.
import { unstable_vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
remix({
- serverBuildDirectory: "dist/server",
- assetsBuildDirectory: "dist/client",
+ buildDirectory: "dist",
})
],
});
Vite: Remove unstable
prefix from serverBundles
option. (#8596)
Vite: Write Vite manifest files to build/.vite
directory rather than being nested within build/client
and build/server
directories. (#8599)
This is a breaking change for consumers of Vite's manifest.json
files.
Vite manifest files are now written to the Remix build directory. Since all Vite manifests are now in the same directory, they're no longer named manifest.json
. Instead, they're named build/.vite/client-manifest.json
and build/.vite/server-manifest.json
, or build/.vite/server-{BUNDLE_ID}-manifest.json
when using server bundles.
Updated dependencies:
@remix-run/server-runtime@2.6.0
@remix-run/node@2.6.0
Vite: Error messages when .server
files are referenced by client (#8267)
- Previously, referencing a
.server
module from client code resulted in an error message like:
The requested module '/app/models/answer.server.ts' does not provide an export named 'isDateType'
- This was confusing because
answer.server.ts
does provide the isDateType
export, but Remix was replacing .server
modules with empty modules (export {}
) for the client build
- Now, Remix explicitly fails at compile time when a
.server
module is referenced from client code and includes dedicated error messages depending on whether the import occurs in a route or a non-route module
- The error messages also include links to relevant documentation
Remove unstable_viteServerBuildModuleId
in favor of manually referencing virtual module name "virtual:remix/server-build"
. (#8264)
This is a breaking change for projects using the unstable Vite plugin with a custom server.
This change was made to avoid issues where @remix-run/dev
could be inadvertently required in your server's production dependencies.
Instead, you should manually write the virtual module name "virtual:remix/server-build"
when calling ssrLoadModule
in development.
-import { unstable_viteServerBuildModuleId } from "@remix-run/dev";
app.all(
"*",
createRequestHandler({
build: vite
- ? () => vite.ssrLoadModule(unstable_viteServerBuildModuleId)
+ ? () => vite.ssrLoadModule("virtual:remix/server-build")
: await import("./build/server/index.js"),
})
);
Vite: Fix errors for non-existent index.html
importer (#8353)
Add vite:dev
and vite:build
commands to the Remix CLI. (#8211)
In order to handle upcoming Remix features where your plugin options can impact the number of Vite builds required, you should now run your Vite dev
and build
processes via the Remix CLI.
{
"scripts": {
- "dev": "vite dev",
- "build": "vite build && vite build --ssr"
+ "dev": "remix vite:dev",
+ "build": "remix vite:build"
}
}
Vite: Preserve names for exports from .client
modules (#8200)
Unlike .server
modules, the main idea is not to prevent code from leaking into the server build
since the client build is already public. Rather, the goal is to isolate the SSR render from client-only code.
Routes need to import code from .client
modules without compilation failing and then rely on runtime checks
or otherwise ensure that execution only happens within a client-only context (e.g. event handlers, useEffect
).
Replacing .client
modules with empty modules would cause the build to fail as ESM named imports are statically analyzed.
So instead, we preserve the named export but replace each exported value with undefined
.
That way, the import is valid at build time and standard runtime checks can be used to determine if the
code is running on the server or client.
Disable watch mode in Vite child compiler during build (#8342)
Vite: Show warning when source maps are enabled in production build (#8222)
Updated dependencies:
@remix-run/server-runtime@2.4.1
@remix-run/node@2.4.1
Vite: exclude modules within .server
directories from client build (#8154)
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.
Vite: Strict route exports (#8171)
With Vite, Remix gets stricter about which exports are allowed from your route modules.
Previously, the Remix compiler would allow any export from routes.
While this was convenient, it was also a common source of bugs that were hard to track down because they only surfaced at runtime.
For more, see https://remix.run/docs/en/main/future/vite#strict-route-exports
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)
Upgrade Vite peer dependency range to v5 (#8172)
Support HMR for routes with handle
export in Vite dev (#8022)
Fix flash of unstyled content for non-Express custom servers in Vite dev (#8076)
Bundle CSS imported in client entry file in Vite plugin (#8143)
Change Vite build output paths to fix a conflict between how Vite and the Remix compiler each manage the public
directory. (#8077)
This is a breaking change for projects using the unstable Vite plugin.
The server is now compiled into build/server
rather than build
, and the client is now compiled into build/client
rather than public
.
For more information on the changes and guidance on how to migrate your project, refer to the updated Remix Vite documentation.
Remove undocumented legacyCssImports
option from Vite plugin due to issues with ?url
imports of CSS files not being processed correctly in Vite (#8096)
Vite: fix access to default entry.{client,server}.tsx
within pnpm workspace on Windows (#8057)
Remove unstable_createViteServer
and unstable_loadViteServerBuild
which were only minimal wrappers around Vite's createServer
and ssrLoadModule
functions when using a custom server. (#8120)
This is a breaking change for projects using the unstable Vite plugin with a custom server.
Instead, we now provide unstable_viteServerBuildModuleId
so that custom servers interact with Vite directly rather than via Remix APIs, for example:
-import {
- unstable_createViteServer,
- unstable_loadViteServerBuild,
-} from "@remix-run/dev";
+import { unstable_viteServerBuildModuleId } from "@remix-run/dev";
Creating the Vite server in middleware mode:
const vite =
process.env.NODE_ENV === "production"
? undefined
- : await unstable_createViteServer();
+ : await import("vite").then(({ createServer }) =>
+ createServer({
+ server: {
+ middlewareMode: true,
+ },
+ })
+ );
Loading the Vite server build in the request handler:
app.all(
"*",
createRequestHandler({
build: vite
- ? () => unstable_loadViteServerBuild(vite)
+ ? () => vite.ssrLoadModule(unstable_viteServerBuildModuleId)
: await import("./build/server/index.js"),
})
);
Pass request handler errors to vite.ssrFixStacktrace
in Vite dev to ensure stack traces correctly map to the original source code (#8066)
Vite: Preserve names for exports from .client imports (#8200)
Unlike .server
modules, the main idea is not to prevent code from leaking into the server build
since the client build is already public. Rather, the goal is to isolate the SSR render from client-only code.
Routes need to import code from .client
modules without compilation failing and then rely on runtime checks
to determine if the code is running on the server or client.
Replacing .client
modules with empty modules would cause the build to fail as ESM named imports are statically analyzed.
So instead, we preserve the named export but replace each exported value with an empty object.
That way, the import is valid at build time and the standard runtime checks can be used to determine if then
code is running on the server or client.
Add @remix-run/node
to Vite's optimizeDeps.include
array (#8177)
Improve Vite plugin performance (#8121)
- Parallelize detection of route module exports
- Disable
server.preTransformRequests
in Vite child compiler since it's only used to process route modules
Remove automatic global Node polyfill installation from the built-in Vite dev server and instead allow explicit opt-in. (#8119)
This is a breaking change for projects using the unstable Vite plugin without a custom server.
If you're not using a custom server, you should call installGlobals
in your Vite config instead.
import { unstable_vitePlugin as remix } from "@remix-run/dev";
+import { installGlobals } from "@remix-run/node";
import { defineConfig } from "vite";
+installGlobals();
export default defineConfig({
plugins: [remix()],
});
Vite: Errors at build-time when client imports .server default export (#8184)
Remix already stripped .server file code before ensuring that server code never makes it into the client.
That results in errors when client code tries to import server code, which is exactly what we want!
But those errors were happening at runtime for default imports.
A better experience is to have those errors happen at build-time so that you guarantee that your users won't hit them.
Fix request instanceof Request
checks when using Vite dev server (#8062)
Updated dependencies:
@remix-run/server-runtime@2.4.0
@remix-run/node@2.4.0
Fix importing of PNGs, SVGs, and other assets from packages in node_modules
(#6813, #7182)
Decouple the @remix-run/dev
package from the contents of the @remix-run/css-bundle
package. (#6982)
- The contents of the
@remix-run/css-bundle
package are now entirely managed by the Remix compiler
- Even though it's still recommended that your Remix dependencies all share the same version, this change ensures that there are no runtime errors when upgrading
@remix-run/dev
without upgrading @remix-run/css-bundle
Allow non-development modes for remix watch
(#7117)
Stop remix dev
when esbuild
is not running (#7158)
Do not interpret JSX in .ts
files (#7306)
While JSX is supported in .js
files for compatibility with existing apps and libraries,
.ts
files should not contain JSX. By not interpreting .ts
files as JSX, .ts
files
can contain single-argument type generics without needing a comma to disambiguate from JSX:
const id = <T>(x: T) => x;
const id = <T,>(x: T) => x;
const id = <T,>(x: T) => x;
// ^ comma: this is a generic, not a JSX element
const component = <h1>hello</h1>;
// ^ no comma: this is a JSX element
Enhance obsolete flag warning for future.v2_dev
if it was an object, and prompt users to lift it to the root dev
config (#7427)
Allow decorators in app code (#7176)
Allow JSX in .js
files during HMR (#7112)
Kill app server when remix dev terminates (#7280)
Support dependencies that import polyfill packages for Node built-ins via a trailing slash (e.g. importing the buffer
package with var Buffer = require('buffer/').Buffer
as recommended in their README) (#7198)
- These imports were previously marked as external
- This meant that they were left as dynamic imports in the client bundle and would throw a runtime error in the browser (e.g.
Dynamic require of "buffer/" is not supported
)
Surface errors when PostCSS config is invalid (#7391)
Restart dev server when Remix config changes (#7269)
Remove outdated ESM import warnings (#6916)
- Most of the time these warnings were false positives.
- Instead, we now rely on built-in Node warnings for ESM imports.
Do not trigger rebuilds when .DS_Store
changes (#7172)
Remove warnings for stabilized flags: (#6905)
unstable_cssSideEffectImports
unstable_cssModules
unstable_vanillaExtract
Allow any mode (NODE_ENV
) (#7113)
Replace the deprecated xdm
package with @mdx-js/mdx
(#4054)
Write a version.txt
sentinel file after server build is completely written (#7299)
Updated dependencies:
@remix-run/server-runtime@2.0.0
built-in tls support (#6483)
New options:
--tls-key
/ tlsKey
: TLS key
--tls-cert
/ tlsCert
: TLS Certificate
If both TLS options are set, scheme
defaults to https
Example
Install mkcert and create a local CA:
brew install mkcert
mkcert -install
Then make sure you inform node
about your CA certs:
export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"
👆 You'll probably want to put that env var in your scripts or .bashrc
/.zshrc
Now create key.pem
and cert.pem
:
mkcert -key-file key.pem -cert-file cert.pem localhost
See mkcert
docs for more details.
Finally, pass in the paths to the key and cert via flags:
remix dev --tls-key=key.pem --tls-cert=cert.pem
or via config:
module.exports = {
future: {
unstable_dev: {
tlsKey: "key.pem",
tlsCert: "cert.pem",
},
},
};
That's all that's needed to set up the Remix Dev Server with TLS.
🚨 Make sure to update your app server for TLS as well.
For example, with express
:
import fs from "node:fs";
import https from "node:https";
import express from "express";
const app = express();
const appServer = https.createServer(
{
key: fs.readFileSync("key.pem"),
cert: fs.readFileSync("cert.pem"),
},
app
);
appServer.listen(3000, () => {
console.log("Ready on https://localhost:3000");
});
Known limitations
remix-serve
does not yet support TLS.
That means this only works for custom app server using the -c
flag for now.
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).
Add caching to PostCSS for regular stylesheets (#6505)
Fix warnings when importing CSS files with future.unstable_dev
enabled (#6506)
Fix Tailwind performance issue when postcss.config.js
contains plugins: { tailwindcss: {} }
and remix.config.js
contains both tailwind: true
and postcss: true
. (#6468)
Note that this was not an issue when the plugin function had been explicitly called, i.e. plugins: [tailwindcss()]
. Remix avoids adding the Tailwind plugin to PostCSS if it's already present but we were failing to detect when the plugin function hadn't been called — either because the plugin function itself had been passed, i.e. plugins: [require('tailwindcss')]
, or the plugin config object syntax had been used, i.e. plugins: { tailwindcss: {} }
.
Faster server export removal for routes when unstable_dev
is enabled. (#6455)
Also, only render modulepreloads on SSR.
Do not render modulepreloads when hydrated.
Add HeadersArgs
type to be consistent with loaders/actions/meta and allows for using a function
declaration in addition to an arrow function expression (#6247)
import type { HeadersArgs } from "@remix-run/node";
export function headers({ loaderHeaders }: HeadersArgs) {
return {
"x-my-custom-thing": loaderHeaders.get("x-my-custom-thing") || "fallback",
};
}
better error message when remix-serve
is not found (#6477)
restore color for app server output (#6485)
Fix route ranking bug with pathless layout route next to a sibling index route (#4421)
- Under the hood this is done by removing the trailing slash from all generated
path
values since the number of slash-delimited segments counts towards route ranking so the trailing slash incorrectly increases the score for routes
Support sibling pathless layout routes by removing pathless layout routes from the unique route path checks in conventional route generation since they inherently trigger duplicate paths (#4421)
fix dev server crashes caused by ungraceful hdr error handling (#6467)
Updated dependencies:
@remix-run/server-runtime@1.17.0
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"
}
}
The Remix dev server spins up your app server as a managed subprocess. (#6133)
This keeps your development environment as close to production as possible.
It 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
remix dev -c "node ./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:
import { broadcastDevReady } from "@remix-run/node";
const BUILD_DIR = path.join(process.cwd(), "build");
app.listen(3000, () => {
const build = require(BUILD_DIR);
console.log("Ready: http://localhost:" + port);
if (process.env.NODE_ENV === "development") {
broadcastDevReady(build);
}
});
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:
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
:
const BUILD_PATH = path.resolve(__dirname, "build");
const watcher = chokidar.watch(BUILD_PATH);
watcher.on("change", () => {
purgeRequireCache();
const build = require(BUILD_PATH);
broadcastDevReady(build);
});