node-serve v0.2.0
-
Add a
setup(app)option toserve()so managed node-serve apps can register native uWebSockets.js WebSocket routes and connection filters before the Fetch fallback route starts listening.import { serve } from 'remix/node-serve' serve(handler, { setup(app) { app.ws('/ws/chat', { message(ws, message, isBinary) { ws.publish('chat', message, isBinary) }, }) app.filter((_res, count) => { console.log(`Active uWS connections: ${count}`) }) }, })
-
Pass native
Requestobjects to Fetch handlers instead of lazy request facades. -
Install
uWebSockets.jsas a required dependency soremix/node-serveworks when package managers omit optional dependencies.
response v0.3.4
- Bumped
@remix-run/*dependencies:
session-middleware v0.3.0
- Added
context.sessionas a direct property installed by the session middleware.context.get(Session)remains supported for keyed context access.
- Bumped
@remix-run/*dependencies:
test v0.4.0
-
Migrated
@remix-run/testfrom thetsxpackage to Remix's internal@remix-run/node-tsxmodule loader.BREAKING CHANGE:
.ts,.tsx, and.jsxmodule loading in@remix-run/testnow uses Remix's internal@remix-run/node-tsxloader. Test modules are still transformed before execution, including JSX and TypeScript syntax that requires JavaScript output, but the loader is now maintained inside Remix.
-
Fix browser test runs so large suites can exceed the per-file timeout as long as individual test files keep reporting progress.
-
Fix
describe.skipanddescribe.onlyso they propagate to nesteddescribeblocks. Previously the skipped/focused state was set only on the outer suite, so tests inside nested describes still ran (or were incorrectly skipped underonly). -
Bumped
@remix-run/*dependencies:
route-pattern v0.21.0
-
BREAKING CHANGE: Remove the
compareFnparameter frommatchandmatchAllmethodsMatches always sort by specificity (most specific first). If you need a different order, sort the result of
matchAllyourself.import * as Specificity from 'remix/route-pattern/specificity' // before matcher.matchAll(url, Specificity.ascending) // after matcher.matchAll(url).sort(Specificity.ascending)
-
BREAKING CHANGE: New modular APIs and subpath exports
Previously, this package shipped the default export and a
/specificityexport. A typical Remix app does not do any client-side matching but all the matching logic would ship to the browser anyway causing JS bloat.Now, features are organized into separate subpath exports, so even without a bundler, only the code you need ends up in the browser. For example, this reduced JS from
route-patternindemos/bookstorefrom 25kB (14.9kB compressed) to 8.8kb (7kB compressed) which amounts to ~65% reduction (~53% reduction compressed).To achieve this, we've reworked our core APIs to be simpler and more independently useful. So instead of a single
RoutePatternclass that does it all (.href,.match, ...), the newRoutePatternclass is a thin layer around the parsed pattern that includesRoutePattern.parsestatic method for parsing and.source,.toString()and.toJSON()for serialization.The rest of the functionality comes from dedicated subpath exports:
- remix/route-pattern/href : Generate hrefs for patterns with type safe params.
- remix/route-pattern/match : Match against one pattern with type inference for params. Or match against many patterns with deterministic ranking and attached data.
- remix/route-pattern/join : Combine two patterns into one. Override protocol, hostname, port. Join pathnames. Merge search constraints.
remix/route-pattern/specificity remains the same as before, providing utilities for ranking matches.
Additionally,
ArrayMatcherandTrieMatcherhave been replaced bycreateMultiMatcher(which is now always backed by trie-based matching). To match against only a single pattern while receiving type safeparamsfrom the match, usecreateMatcher.See the new README for details.
-
Encode href params so pathname params cannot inject URL path, dot segment, query, or hash syntax. Wildcard pathname params now preserve slash-separated structure while encoding each segment, and hostname params are normalized or rejected when they would inject URL authority, path, query, or hash syntax. Matchers now decode generated pathname params so reserved characters like
/,?, and#round-trip as param content. -
Do not allow partial matches for variables and wildcards in pathname
let matcher = createMultiMatcher<string>() matcher.add('/files/:name.md', 'original') matcher.add('/files/:name.md.backup', 'backup') // before: 'original' included since `:name.md` partially matches `readme.md.backup` matcher.matchAll('https://example.com/files/readme.md.backup').map((match) => match.data) // ❌ ['backup', 'original'] // after: only matches when the pattern covers the whole segment matcher.matchAll('https://example.com/files/readme.md.backup').map((match) => match.data) // ✅ ['backup']
-
Fix matcher to match origin-less patterns (e.g.
/,/about) against URLs that have an explicit port. Previously a pattern like/would not matchhttp://localhost:44199/.
ui v0.1.2
-
Fix a bug in Safari where cross-origin links to a new subdomain incorrectly set
event.canIntercept=trueand try to opt-into a<Frame>navigation which fails. Cross-origin links now correctly fall through to a document navigation in Safari. -
Keep streamed frame content in its template when a resolved frame stream starts with a doctype-only chunk.
-
Emit the built-in theme reset in
rmx-resetso generated Remix UI component styles can override it. Document where app layers should sit relative to Remix UI layers. -
Fixed layout animation interruptions so they restart from their current position and don't restart for updates that don't change their final position.
-
Improved type inference for
onmixinWhen defining a wrapper for
on, usetargetgeneric on your handler type:import { on, type Dispatched } from '@remix-run/ui' const ACCORDION_CHANGE_EVENT = 'rmx:accordion-change' as const type AccordionChangeEvent = Event & { accordionType: 'single' | 'multiple' itemValue: string value: string | null | string[] } declare global { interface HTMLElementEventMap { [ACCORDION_CHANGE_EVENT]: AccordionChangeEvent } } type AccordionChangeHandler<target extends HTMLElement> = ( event: Dispatched<AccordionChangeEvent, target>, signal: AbortSignal, ) => void | Promise<void> export function onAccordionChange<target extends HTMLElement>( handler: AccordionChangeHandler<target>, captureBoolean?: boolean, ) { return on(ACCORDION_CHANGE_EVENT, handler, captureBoolean) } let button = ( <button mix={[ onAccordionChange((event, signal) => { event // ^? Dispatched<AccordionChangeEvent, HTMLButtonElement> event.currentTarget // ^? HTMLButtonElement }), ]} /> )
-
Preserve hydrated client entry instances and nested frame resolution during full-document root frame reloads.
-
Document the
run()loadModuleandresolveFramehooks so editor hints explain how to hydrate client entries and resolve browser-loaded frames. -
Optimize UI runtime hot paths.
- Fast path for plain
on()mixins that patches host listeners in place. - Lazy direct listener closures for event listeners managed by the runtime.
- Lazy mixin scope signals to avoid unnecessary AbortController work.
- Faster keyed reconciliation for in-order, append-only, single-removal, and pair-swap lists.
- Property-level patching for object styles during updates.
- Bulk clearing for removable child lists, with an innerHTML guard.
- Fast path for plain
-
Fix a flash of unstyled content when navigating between two pages whose hydrated client entries use different
css()rules. Style adoption now releases prior-page server styles by refcount instead of resetting the adopted stylesheet, so DOM preserved across a reload (e.g. inside a still-hydrated client-entry boundary) keeps its rules until the new module finishes loading and replaces it. -
Fix server rendering for
<textarea value>,<textarea defaultValue>,<input defaultValue>, and<input defaultChecked>so initial form control content matches client rendering, and disallow textarea children in JSX types.
static-middleware v0.4.9
- Bumped
@remix-run/*dependencies:
render-middleware v0.1.0
- Initial release of
@remix-run/render-middleware, which provides theRenderercontext key,Renderertype, andrenderWith()middleware for adding request-scoped renderers tofetch-routerrequest context. Renderers are available as bothcontext.renderandcontext.get(Renderer).
- Bumped
@remix-run/*dependencies:
remix v3.0.0-beta.1
-
BREAKING CHANGE: Removed the
ContextWithAuthandContextWithRequiredAuthhelper types fromremix/auth-middleware. Derive auth-aware request context from the actual auth middleware tuple withMiddlewareContext, or use the coreContextWithEntryhelper fromremix/fetch-routerwhen manually composing context types without a middleware tuple.import { requireAuth } from 'remix/auth-middleware' import type { MiddlewareContext } from 'remix/fetch-router' let protectedMiddleware = [requireAuth<AuthIdentity>()] as const type AppAuthContext = MiddlewareContext<typeof protectedMiddleware, AppContext>
-
BREAKING CHANGE: Remix app scaffolding,
remix doctor, andremix routesnow useapp/actionswith controller files only. The oldapp/controllersdirectory name has been replaced byapp/actions, and root route actions should no longer live in standalone files.Move route controllers from
app/controllerstoapp/actions, consolidate root route actions intoapp/actions/controller.tsx, and map nested route maps explicitly inapp/router.tswith onerouter.map(...)call per route map. Controller middleware applies only to direct actions owned by that controller. -
BREAKING CHANGE: Removed the
ContextWithRendererhelper type fromremix/render-middleware. Derive renderer-aware request context from therenderWith()middleware tuple withMiddlewareContext, or use the coreContextWithEntryhelper fromremix/fetch-routerwhen manually composing context types without a middleware tuple.import { renderWith } from 'remix/render-middleware' import type { MiddlewareContext } from 'remix/fetch-router' let render = renderWith(() => (value: string) => new Response(value)) type AppContext = MiddlewareContext<[typeof render]>
-
BREAKING CHANGE:
remix testandremix/testnow use Remix's internalnode-tsxloader instead of thetsxpackage.Test modules are still transformed before execution, including JSX and TypeScript syntax that requires JavaScript output, but the loader is now maintained inside Remix through
remix/node-tsx. -
BREAKING CHANGE:
remix/async-context-middlewareno longer exposesAsyncContextTypes.getContext()now derives its type fromremix/fetch-router'sRouterTypes.context, with route params broadened toAnyParams, so apps only need the router context augmentation. -
BREAKING CHANGE: Updated the re-exported
remix/fetch-routerhelper types around full request-context types and stored route handlers.Action,Controller, andRequestHandlernow take the full request context type,MiddlewareContextaccepts middleware values plus an optional base context, andcreateAction()/createController()are the preferred helpers for stored handlers.For most apps, configure
RouterTypes.contextonce and letcreateController()infer route action context from the route map and controller middleware:declare module 'remix/fetch-router' { interface RouterTypes { context: AppContext } } let accountMiddleware = [requireAuth<AuthIdentity>()] as const let controller = createController(routes, { middleware: accountMiddleware, actions: { account(context) { return Response.json(context.auth.identity) }, }, })
Low-level context transform helpers such as
BuildAction,MiddlewareContextTransform,ContextTransform,ApplyContextTransform,ApplyMiddleware, andApplyMiddlewareTupleare no longer exported. UseContextWithParams,ContextWithEntry,ContextWithEntries,MiddlewareContext, andRouteEntrywhen manually composing request context or custom matcher payloads. -
BREAKING CHANGE: In
remix/route-pattern, remove thecompareFnparameter frommatchandmatchAll.Matches always sort by specificity (most specific first). If you need a different order, sort the result of
matchAllyourself.import * as Specificity from 'remix/route-pattern/specificity' // before matcher.matchAll(url, Specificity.ascending) // after matcher.matchAll(url).sort(Specificity.ascending)
-
BREAKING CHANGE: New modular
remix/route-patternAPIs and subpath exportsPreviously,
remix/route-patternbundled URL generation, matching, and specificity helpers into one entrypoint. A typical Remix app does not do any client-side matching, but all the matching logic would ship to the browser anyway, causing JS bloat.Now, route pattern features are organized into separate subpath exports, so even without a bundler, only the code you need ends up in the browser:
remix/route-pattern/hrefgenerates hrefs for patterns with type-safe params.remix/route-pattern/matchmatches against one pattern with type inference for params, or against many patterns with deterministic ranking and attached data.remix/route-pattern/joincombines two patterns into one, including protocol, hostname, port, pathname, and search constraints.remix/route-pattern/specificitycontinues to provide utilities for ranking matches.
The base
remix/route-patternexport now focuses on parsing and serializing route patterns. -
Expose
@remix-run/node-tsxthroughremix/node-tsxandremix/node-tsx/load-module.Use
node --import remix/node-tsxto run.ts,.tsx, and.jsxfiles directly in Node.js with TypeScript and JSX syntax support. The loader transforms TypeScript syntax that requires JavaScript output, including enums, runtime namespaces, and parameter properties, while preserving Node.js module resolution. -
Updated the
remixpackage with domain-oriented exports, no longer only mapping 1:1 to underlying@remix-run/*packages. Existing 1:1 package exports remain available during the beta migration and will be removed before a Remix 3.0.0 stable release.Preferred package mappings:
remix/async-context-middleware→remix/middleware/async-contextremix/auth-middleware→remix/middleware/authremix/compression-middleware→remix/middleware/compressionremix/cop-middleware→remix/middleware/copremix/cors-middleware→remix/middleware/corsremix/csrf-middleware→remix/middleware/csrfremix/data-table-mysql→remix/data-table/mysqlremix/data-table-postgres→remix/data-table/postgresremix/data-table-sqlite→remix/data-table/sqliteremix/fetch-router→remix/routerremix/fetch-router/routes→remix/routesremix/file-storage-s3→remix/file-storage/s3remix/form-data-middleware→remix/middleware/form-dataremix/logger-middleware→remix/middleware/loggerremix/method-override-middleware→remix/middleware/method-overrideremix/render-middleware→remix/middleware/renderremix/session-middleware→remix/middleware/sessionremix/session-storage-memcache→remix/session-storage/memcacheremix/session-storage-redis→remix/session-storage/redisremix/session/cookie-storage→remix/session-storage/cookieremix/session/fs-storage→remix/session-storage/fsremix/session/memory-storage→remix/session-storage/memoryremix/static-middleware→remix/middleware/static
-
Added support for middleware-installed direct request context properties through
remix/fetch-router, including the newContextEntrytype for object-shaped context entries. Built-in middleware now uses this forcontext.auth,context.formData,context.logger,context.render, andcontext.session; keyed access withcontext.get(...)remains supported. -
Expose the
node-servesetup(app)option throughremix/node-serveso apps can register native uWebSockets.js WebSocket routes and connection filters before the Fetch fallback route starts listening.import { serve } from 'remix/node-serve' serve(handler, { setup(app) { app.ws('/ws/chat', { message(ws, message, isBinary) { ws.publish('chat', message, isBinary) }, }) }, })
-
Include source-adjacent README files for generated
remix/*exports in the published package so package managers and tooling can discover the relevant module documentation fromnode_modules/remix. -
Fix
remix/node-fetch-serverso streaming responses write the first chunk immediately instead of waiting for another chunk. -
Fix matching so dynamic pathname segments and wildcard continuations only match when they cover the full pathname range being tested.
import { createMultiMatcher } from 'remix/route-pattern' let matcher = createMultiMatcher<string>() matcher.add('/files/:name.md', 'markdown') matcher.add('/files/:name.md.backup', 'backup') // before: matched both patterns because `/files/:name.md` matched a prefix of the segment matcher.matchAll('https://example.com/files/readme.md.backup').map((match) => match.data) // ['backup', 'markdown'] // after: only matches when the pattern covers the whole segment matcher.matchAll('https://example.com/files/readme.md.backup').map((match) => match.data) // ['backup']
-
Bumped
@remix-run/*dependencies:assets@0.4.0async-context-middleware@0.3.0auth@0.2.2auth-middleware@0.2.0cli@0.3.0compression-middleware@0.1.8cookie@0.5.2cop-middleware@0.1.3cors-middleware@0.1.3csrf-middleware@0.1.3data-table@0.3.0data-table-mysql@0.4.0data-table-postgres@0.4.0data-table-sqlite@0.5.0fetch-proxy@0.8.1fetch-router@0.19.0form-data-middleware@0.3.0form-data-parser@0.17.1headers@0.20.0logger-middleware@0.3.0method-override-middleware@0.1.8multipart-parser@0.16.1node-fetch-server@0.13.2node-serve@0.2.0node-tsx@0.1.0render-middleware@0.1.0response@0.3.4route-pattern@0.21.0session-middleware@0.3.0static-middleware@0.4.9test@0.4.0ui@0.1.2