Pre-release Changes
-
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