How to Adopt All Future Flags
May 21, 2025
Future Flag Migration for React Router 7
Before upgrading to React Router 7, you need to make sure to adopt all Remix future flags. The CLI upgrade command (npx shopify hydrogen upgrade) provides guides on how to do this, but this guide consolidates all the steps.
First of all, the legacy classic Remix compiler is no longer supported, and you must upgrade to Vite if you haven't already. After running npx shopify hydrogen upgrade, run npx shopify hydrogen setup vite to migrate to Vite.
Next enable each of these future flags. It's easiest to do one at a time, making sure the app is functional after each step. At the end you should have all future flags enabled within the remix vite plugin:
Code Example
remix({ presets: [hydrogen.v3preset()], future: { v3_fetcherPersist: true, v3_relativeSplatPath: true, v3_throwAbortReason: true, v3_lazyRouteDiscovery: true, v3_routeConfig: true, v3_singleFetch: true, }, })
v3_fetcherPersist
Enabling this flag is unlikely to affect your app. See the Remix docs for more information.
v3_relativeSplatPath
If you have any routes with a path + a splat like dashboard.$.tsx that have relative links like <Link to="relative"> or <Link to="../relative"> beneath it, you will need to update your code.
See the Remix docs for more information.
v3_throwAbortReason
When a server-side request is aborted, such as when a user navigates away from a page before the loader finishes, Remix will throw the request.signal.reason instead of an error like new Error("query() call aborted..."). Enabling this is unlikely to affect your app.
See the Remix docs for more information.
v3_lazyRouteDiscovery
Unlikely to affect your app. See the Remix docs for more information.
v3_singleFetch
Single Fetch is a new data loading strategy and streaming format. For more detailed upgrade instructions, see the v3_singleFetch PR or refer to the Remix guide.
-
In your entry.server.tsx, add nonce to the <RemixServer>:
Code Example
const body = await renderToReadableStream( <NonceProvider> <RemixServer context={remixContext} url={request.url} + nonce={nonce} /> </NonceProvider> );
-
Return plain objects from loaders and actions instead of using json or defer:
Code Example
// Before import {json} from "@shopify/remix-oxygen"; export async function loader({}: LoaderFunctionArgs) { let tasks = await fetchTasks(); return json(tasks); } // After export async function loader({}: LoaderFunctionArgs) { let tasks = await fetchTasks(); return tasks; }
Code Example
// When setting headers import {data} from "@shopify/remix-oxygen"; export async function loader({}: LoaderFunctionArgs) { let tasks = await fetchTasks(); return data(tasks, { headers: { "Cache-Control": "public, max-age=604800" } }); }
Update your shouldRevalidate in root.tsx:
Code Example
export const shouldRevalidate: ShouldRevalidateFunction = ({ formMethod, currentUrl, nextUrl, }) => { if (formMethod && formMethod !== 'GET') return true; if (currentUrl.toString() === nextUrl.toString()) return true; // Defaulting to no revalidation for root loader data to improve performance. // When using this feature, you risk your UI getting out of sync with your server. // Use with caution. If you are uncomfortable with this optimization, update the // line below to `return defaultShouldRevalidate` instead. // For more details see: https://remix.run/docs/en/main/route/should-revalidate return false; };
v3_routeConfig
Config-based routing is the new default in React Router v7, configured via the routes.ts file. Support for routes.ts in Remix is a migration path toward React Router v7.
- Install the package: npm install -D @remix-run/route-config @remix-run/route-config
- Add a routes.ts file:
Code Example
import {flatRoutes} from '@remix-run/fs-routes'; import {type RouteConfig} from '@remix-run/route-config'; import {hydrogenRoutes} from '@shopify/hydrogen'; export default hydrogenRoutes([ ...(await flatRoutes()), ]) satisfies RouteConfig;
Update vite.config.js to use hydrogen.v3preset() and enable v3_routeConfig:
Code Example
export default defineConfig({ plugins: [ hydrogen(), oxygen(), remix({ presets: [hydrogen.v3preset()], future: { v3_fetcherPersist: true, v3_relativeSplatPath: true, v3_throwAbortReason: true, v3_lazyRouteDiscovery: true, v3_singleFetch: true, v3_routeConfig: true, }, }), tsconfigPaths(), ], });
See the Remix docs for more information.
Get building
Spin up a new Hydrogen app in minutes.
See documentation