-
Notifications
You must be signed in to change notification settings - Fork 23
Upgrading from Remix
- What is released?: A new package & template
- Polaris Web components: RR package is all-in on Polaris WC. These work nicely with React.
-
A simpler package: REST, non-embedded and
AppProxyFormhave low adoption and won't be ported from Remix.
The July 2025 release is an early access release. Most apps will be fine to adopt React Router. If you encounter issues please provide feedback.
It's very similar: The RR package is a fork of the Remix package and the most commonly used API's are not changing.
Detailed migration: We're detailing each step and providing example migration branches. As we migrate more apps we'll improve this guide even more.
Polaris Web Components: The RR package uses Polaris Web Components, which automatically match Shopify's admin design and update as the platform evolves. This provides merchants with a consistent experience, reduces your maintenance overhead, and allows you to focus on core business functionality. These components share APIs with other Shopify surfaces, simplifying development.
Limited Remix V3 Support: The RR template will be prioritized over the Remix template. Support for the Remix package in its current state will be limited to critical security issues.
Some features with very low adoption are being removed from the RR package. Don't worry: Most Remix apps don't use these features and there are alternatives. These features will still be supported by Shopify, they just won't be part of the RR package.
If you don't use REST you can skip this section.
We previously communicated we are all in on GraphQL and added a future flag to the Remix package to remove REST. We have removed REST from the RR package.
You can either migrate to GraphQL, or use the REST client directly.
Most Remix apps do not use REST and are unaffected by this change.
If you don't know what this means, your app is probably embedded. You can skip this section if your app is embedded.
Around 2% of Remix apps render in a separate tab or window (Non-embedded). This merchant UX is not as good so we are dropping support for this in the RR package. If your app is not embedded you can migrate to embedded, or stay on the Remix package.
Around 98% of Remix apps are unaffected by this change.
If your app was created after January 25th 2024 and remained embedded, or if your adopted the unstable_newEmbeddedAuthStrategy future flag you can skip this section.
Let's explain 2 auth strategies:
-
Auth Code flow: This is our legacy mechanism. It supports non-embedded & embedded. It has an inferior merchant UX. It is enabled if the
unstable_newEmbeddedAuthStrategyfuture flag is not true -
Shopify managed install: This uses token exchange and is our default. It has a much superior merchant UX. It is enabled if the
unstable_newEmbeddedAuthStrategyfuture flag is true.
If your app is embedded, you should enable the unstable_newEmbeddedAuthStrategy flag before migrating to RR. If your app is not embedded, please see the previous section.
Most Remix apps are unaffected by this change.
If your app does not use AppProxyForm, you can skip this section.
If your app uses AppProxyForm, you should switch to using lowercase <form>, rather than <AppProxyForm/>. If this change causes you issues please provide feedback so we can understand your use case. There may be better solutions like Customer Account Extensions.
A very small percent of Remix apps are affected by this change.
You can create a new Shopify app that uses React Router using this command:
shopify app init --template=https://github.com/Shopify/shopify-app-template-react-routerPlease see the CLI documentation for more information.
If you prefer to see all the changes we've prepared 2 migration branches:
- Most apps can use the RR App template migration branch
- Use the QR Code Example migration branch if you don't have the
vite.configfile.
Detailed instructions follow below
If your app was created after February 21st 2024, or you have already adopted Vite, you can skip this section.
If your app was created before February 21st 2024, you may need to adopt Vite. Here are some resources to follow:
- Migrate your Remix app to Vite explains how to migrate a Shopify Remix app to Vite.
- This commit shows how to apply these changes.
- The official docs that describe this change.
These steps are described in the official Remix future flags guide.
Depending on when your app was created, you may be able to skip some of these steps:
- Remove
installGlobals:
vite.config.ts
- import { installGlobals } from "@remix-run/node";
- installGlobals({ nativeFetch: true });package.json
"engines": {
- "node": "^18.20 || ^20.10 || >=21.0.0"
+ "node": ">=20.10"
},A commit showing these changes
- Update the tsconfig:
tsconfig.json
- "types": ["node"]
+ "types": ["@remix-run/node", "vite/client"]A commit showing these changes
- Adopt
v3_singleFetch:
vite.config.ts
remix({
ignoredRouteFiles: ["**/.*"],
future: {
v3_fetcherPersist: true,
v3_relativeSplatPath: true,
v3_throwAbortReason: true,
v3_lazyRouteDiscovery: true,
+ v3_singleFetch: true,
v3_routeConfig: true,
},
})Single fetch changes how Response headers are managed. Because Shopify app's frequently send Responses with specific headers you must make sure to always include a headers export in every route that calls authenticate.admin(request).
/app/routes/**.tsx
+ import { boundary } from "@shopify/shopify-app-remix/server";
+ import type { HeadersFunction } from "@remix-run/node";
export const loader = async ({ request }: LoaderFunctionArgs) => {
await authenticate.admin(request);
return null;
};
+ export const headers: HeadersFunction = (headersArgs) => {
+ return boundary.headers(headersArgs);
+ };A commit showing these changes
- (Optional) Update silent config by replacing its content:
.eslintrc.cjs
/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
commonjs: true,
es6: true,
},
ignorePatterns: ['!**/.server', '!**/.client'],
// Base config
extends: ['eslint:recommended'],
overrides: [
// React
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: ['react', 'jsx-a11y'],
extends: [
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
],
settings: {
react: {
version: 'detect',
},
formComponents: ['Form'],
linkComponents: [
{name: 'Link', linkAttribute: 'to'},
{name: 'NavLink', linkAttribute: 'to'},
],
'import/resolver': {
typescript: {},
},
},
},
// Typescript
{
files: ['**/*.{ts,tsx}'],
plugins: ['@typescript-eslint', 'import'],
parser: '@typescript-eslint/parser',
settings: {
'import/internal-regex': '^~/',
'import/resolver': {
node: {
extensions: ['.ts', '.tsx'],
},
typescript: {
alwaysTryTypes: true,
},
},
},
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
],
},
// Node
{
files: ['.eslintrc.cjs'],
env: {
node: true,
},
},
],
};A commit showing these changes
These steps are described in the official upgrade from Remix guide.
- Swap to React Router. First run the codemod:
npx codemod remix/2/react-router/upgrade && npm installNow update the reactRouter() config to remove future flags:
vite.config.ts
- reactRouter({
- ignoredRouteFiles: ["**/.*"],
- future: {
- v3_fetcherPersist: true,
- v3_relativeSplatPath: true,
- v3_throwAbortReason: true,
- v3_lazyRouteDiscovery: true,
- v3_singleFetch: true,
- v3_routeConfig: true,
- },
- }),
+ reactRouter()A commit showing these changes, 99% of which are created by the codemod.
- Tell the Shopify CLI how to build your app.
shopify.web.toml
- name = "remix"
+ name = "React Router"
[commands]
predev = "npx prisma generate"
- dev = "npx prisma migrate deploy && npm exec remix vite:dev"
+ dev = "npx prisma migrate deploy && npm exec react-router dev"A commit showing these changes
- React Router can automatically generate types for your routes. Let's make sure the app is setup for that now:
.gitignore
+ # Hide files auto-generated by react router
+ .react-router/env.d.ts
- /// <reference types="@remix-run/node" />
+ /// <reference types="@react-router/node" />tsconfig.json
- "include": ["env.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": ["env.d.ts", "**/*.ts", "**/*.tsx", ".react-router/types/**/*"],
"compilerOptions": {
- "types": ["@remix-run/node", "vite/client"]
+ "types": ["@react-router/node", "vite/client"],
+ "rootDirs": [".", "./.react-router/types"]
}A commit showing these changes
Now that React Router is set up it's time to swap @shopify/shopify-app-remix for @shopify/shopify-app-react-router.
- Swap dependencies:
package.json
- "@shopify/shopify-app-remix": "^3.7.0",
+ @shopify/shopify-app-react-router": "0.0.0",app/shopify.server.ts
- import "@shopify/shopify-app-remix/adapters/node";
- import { ApiVersion, AppDistribution, shopifyApp} from "@shopify/shopify-app-remix/server";
+ import "@shopify/shopify-app-react-router/adapters/node";
+ import { ApiVersion, AppDistribution, shopifyApp} from "@shopify/shopify-app-react-router/server";app/routes/**.tsx
- import { boundary } from "@shopify/shopify-app-remix/server";
+ import { boundary } from "@shopify/shopify-app-react-router/server";app/routes/auth.login/error.server.tsx
- import type { LoginError } from "@shopify/shopify-app-remix/server";
- import { LoginErrorType } from "@shopify/shopify-app-remix/server";
+ import type { LoginError } from "@shopify/shopify-app-react-router/server";
+ import { LoginErrorType } from "@shopify/shopify-app-react-router/server";A commit showing these changes
- Previously the Remix package exported an
AppProviderthat configured App Bridge and Polaris. Given how simple App Bridge has become and how simple Polaris Web Components are, this no longer makes sense. Let's configure App Bridge and Polaris Web Components:
app/routes/app.tsx
- import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
- export const links = () => [{ rel: "stylesheet", href: polarisStyles }];
- <AppProvider isEmbeddedApp apiKey={apiKey}>
+ <AppProvider embedded apiKey={apiKey}>Make sure to include any custom code you added to app.tsx like NavMenu items.
app/routes/auth.login/route.tsx
import {
- AppProvider as PolarisAppProvider,
Button,
Card,
FormLayout,
Page,
Text,
TextField,
} from "@shopify/polaris";
- import polarisTranslations from "@shopify/polaris/locales/en.json";
- import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
+ import { AppProvider } from "@shopify/shopify-app-react-router/react";
- export const links = () => [{ rel: "stylesheet", href: polarisStyles }];
export const loader = async ({ request }: LoaderFunctionArgs) => {
const errors = loginErrorMessage(await login(request));
- return { errors, polarisTranslations };
+ return { errors };
};
export default function Auth() {
return (
- <PolarisAppProvider i18n={loaderData.polarisTranslations}>
+ <AppProvider embedded={false}>
- </AppProvider>
+ </>
);
}package.json
"dependencies": {
+ "@shopify/app-bridge-ui-types": "^0.1.1",
},A commit that shows these changes
You are ready to upgrade your UI to Polaris Web Components.
First update app/routes/auth.login/route.tsx
- import {
- Button,
- Card,
- FormLayout,
- Page,
- Text,
- TextField,
- } from "@shopify/polaris";
export default function Auth() {More actions
const loaderData = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
const [shop, setShop] = useState("");
const { errors } = actionData || loaderData;
return (
<AppProvider embedded={false}>
- <Page>Add commentMore actions
- <Card>
- <Form method="post">
- <FormLayout>
- <Text variant="headingMd" as="h2">
- Log in
- </Text>
- <TextField
- type="text"
- name="shop"
- label="Shop domain"
- helpText="example.myshopify.com"
- value={shop}
- onChange={setShop}
- autoComplete="on"
- error={errors.shop}
- />
- <Button submit>Log in</Button>
- </FormLayout>
- </Form>
- </Card>
- </Page>
+ <s-page>Add commentMore actions
+ <Form method="post">
+ <s-section heading="Log in">
+ <s-text-field
+ type="text"
+ name="shop"
+ label="Shop domain"
+ helpText="example.myshopify.com"
+ value={shop}
+ onChange={setShop}
+ autoComplete="on"
+ error={errors.shop}
+ ></s-text-field>
+ <s-button submit>Log in</s-button>
+ </s-section>
+ </Form>
</s-page>
</AppProvider>
)Then upgrade your custom UI to Polaris Web components. Some resource to help:
- Polaris Web Components
- Upgrading the QR Code example app from Polaris React to Polaris Web Components.
- Upgrading the App template from Polaris React to Polaris Web Components.