Skip to content
This repository was archived by the owner on Jan 26, 2026. It is now read-only.

Commit 71812cd

Browse files
committed
chore: update package dependencies in package.json and package-lock.json to latest versions, including @remix-run and @Shopify packages; refactor loader functions to use data() instead of json() in multiple routes; add v3_singleFetch option in vite.config.ts
1 parent 6084ab4 commit 71812cd

13 files changed

+488
-190
lines changed
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# Hydrogen upgrade guide: 2025.1.0 to 2025.1.1
2+
3+
----
4+
5+
## Features
6+
7+
### Enable Remix `v3_singleFetch` future flag [#2708](https://github.com/Shopify/hydrogen/pull/2708)
8+
9+
#### Step: 1. In your `vite.config.ts`, add the single fetch future flag [#2708](https://github.com/Shopify/hydrogen/pull/2708)
10+
11+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
12+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
13+
```diff
14+
+ declare module "@remix-run/server-runtime" {
15+
+ interface Future {
16+
+ v3_singleFetch: true;
17+
+ }
18+
+ }
19+
20+
export default defineConfig({
21+
plugins: [
22+
hydrogen(),
23+
oxygen(),
24+
remix({
25+
presets: [hydrogen.preset()],
26+
future: {
27+
v3_fetcherPersist: true,
28+
v3_relativeSplatPath: true,
29+
v3_throwAbortReason: true,
30+
v3_lazyRouteDiscovery: true,
31+
+ v3_singleFetch: true,
32+
},
33+
}),
34+
tsconfigPaths(),
35+
],
36+
```
37+
38+
#### Step: 2. In your `entry.server.tsx`, add `nonce` to the `<RemixServer>` [#2708](https://github.com/Shopify/hydrogen/pull/2708)
39+
40+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
41+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
42+
```diff
43+
const body = await renderToReadableStream(
44+
<NonceProvider>
45+
<RemixServer
46+
context={remixContext}
47+
url={request.url}
48+
+ nonce={nonce}
49+
/>
50+
</NonceProvider>,
51+
```
52+
53+
#### Step: 3. Update the shouldRevalidate function in root.tsx [#2708](https://github.com/Shopify/hydrogen/pull/2708)
54+
55+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
56+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
57+
```diff
58+
export const shouldRevalidate: ShouldRevalidateFunction = ({
59+
formMethod,
60+
currentUrl,
61+
nextUrl,
62+
- defaultShouldRevalidate,
63+
}) => {
64+
// revalidate when a mutation is performed e.g add to cart, login...
65+
if (formMethod && formMethod !== 'GET') return true;
66+
67+
// revalidate when manually revalidating via useRevalidator
68+
if (currentUrl.toString() === nextUrl.toString()) return true;
69+
70+
- return defaultShouldRevalidate;
71+
+ return false;
72+
};
73+
```
74+
75+
#### Step: 4. Update `cart.tsx` to add a headers export and update to `data` import usage [#2708](https://github.com/Shopify/hydrogen/pull/2708)
76+
77+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
78+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
79+
```diff
80+
import {
81+
- json,
82+
+ data,
83+
type LoaderFunctionArgs,
84+
type ActionFunctionArgs,
85+
type HeadersFunction
86+
} from '@shopify/remix-oxygen';
87+
+ export const headers: HeadersFunction = ({actionHeaders}) => actionHeaders;
88+
89+
export async function action({request, context}: ActionFunctionArgs) {
90+
...
91+
- return json(
92+
+ return data(
93+
{
94+
cart: cartResult,
95+
errors,
96+
warnings,
97+
analytics: {
98+
cartId,
99+
},
100+
},
101+
{status, headers},
102+
);
103+
}
104+
105+
export async function loader({context}: LoaderFunctionArgs) {
106+
const {cart} = context;
107+
- return json(await cart.get());
108+
+ return await cart.get();
109+
}
110+
```
111+
112+
#### Step: 5. Deprecate `json` and `defer` import usage from `@shopify/remix-oxygen` [#2708](https://github.com/Shopify/hydrogen/pull/2708)
113+
114+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
115+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
116+
```diff
117+
- import {json} from "@shopify/remix-oxygen";
118+
119+
export async function loader({}: LoaderFunctionArgs) {
120+
let tasks = await fetchTasks();
121+
- return json(tasks);
122+
+ return tasks;
123+
}
124+
```
125+
126+
```diff
127+
- import {defer} from "@shopify/remix-oxygen";
128+
129+
export async function loader({}: LoaderFunctionArgs) {
130+
let lazyStuff = fetchLazyStuff();
131+
let tasks = await fetchTasks();
132+
- return defer({ tasks, lazyStuff });
133+
+ return { tasks, lazyStuff };
134+
}
135+
```
136+
137+
138+
#### Step: 6. If you were using the second parameter of json/defer to set a custom status or headers on your response, you can continue doing so via the new data API: [#2708](https://github.com/Shopify/hydrogen/pull/2708)
139+
140+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
141+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
142+
```diff
143+
- import {json} from "@shopify/remix-oxygen";
144+
+ import {data, type HeadersFunction} from "@shopify/remix-oxygen";
145+
146+
+ /**
147+
+ * If your loader or action is returning a response with headers,
148+
+ * make sure to export a headers function that merges your headers
149+
+ * on your route. Otherwise, your headers may be lost.
150+
+ * Remix doc: https://remix.run/docs/en/main/route/headers
151+
+ **/
152+
+ export const headers: HeadersFunction = ({loaderHeaders}) => loaderHeaders;
153+
154+
export async function loader({}: LoaderFunctionArgs) {
155+
let tasks = await fetchTasks();
156+
- return json(tasks, {
157+
+ return data(tasks, {
158+
headers: {
159+
"Cache-Control": "public, max-age=604800"
160+
}
161+
});
162+
}
163+
```
164+
165+
166+
#### Step: 7. If you are using legacy customer account flow or multipass, there are a couple more files that requires updating: [#2708](https://github.com/Shopify/hydrogen/pull/2708)
167+
168+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
169+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
170+
```diff
171+
+ export const headers: HeadersFunction = ({loaderHeaders}) => loaderHeaders;
172+
```
173+
174+
175+
#### Step: 8. In `routes/account_.register.tsx`, add a `headers` export for `actionHeaders` [#2708](https://github.com/Shopify/hydrogen/pull/2708)
176+
177+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
178+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
179+
```diff
180+
+ export const headers: HeadersFunction = ({actionHeaders}) => actionHeaders;
181+
```
182+
183+
184+
#### Step: 9. If you are using multipass, in `routes/account_.login.multipass.tsx` [#2708](https://github.com/Shopify/hydrogen/pull/2708)
185+
186+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
187+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
188+
```diff
189+
+ export const headers: HeadersFunction = ({actionHeaders}) => actionHeaders;
190+
```
191+
192+
193+
#### Step: 10. Update all `json` response wrapper to `remixData` [#2708](https://github.com/Shopify/hydrogen/pull/2708)
194+
195+
[docs](https://remix.run/docs/en/main/guides/single-fetch)
196+
[#2708](https://github.com/Shopify/hydrogen/pull/2708)
197+
```diff
198+
import {
199+
- json,
200+
+ data as remixData,
201+
} from '@shopify/remix-oxygen';
202+
203+
- return json(
204+
+ return remixData(
205+
...
206+
);
207+
```
208+
209+
### B2B methods and props are now stable [#2736](https://github.com/Shopify/hydrogen/pull/2736)
210+
211+
#### Step: 1. Search for anywhere using `UNSTABLE_getBuyer` and `UNSTABLE_setBuyer` is update accordingly [#2736](https://github.com/Shopify/hydrogen/pull/2736)
212+
213+
[#2736](https://github.com/Shopify/hydrogen/pull/2736)
214+
```diff
215+
- customerAccount.UNSTABLE_getBuyer();
216+
+ customerAccount.getBuyer()
217+
218+
- customerAccount.UNSTABLE_setBuyer({
219+
+ customerAccount.setBuyer({
220+
companyLocationId,
221+
});
222+
```
223+
224+
#### Step: 2. Update `createHydrogenContext` to remove the `unstableB2b` option [#2736](https://github.com/Shopify/hydrogen/pull/2736)
225+
226+
[#2736](https://github.com/Shopify/hydrogen/pull/2736)
227+
```diff
228+
const hydrogenContext = createHydrogenContext({
229+
env,
230+
request,
231+
cache,
232+
waitUntil,
233+
session,
234+
i18n: {language: 'EN', country: 'US'},
235+
- customerAccount: {
236+
- unstableB2b: true,
237+
- },
238+
cart: {
239+
queryFragment: CART_QUERY_FRAGMENT,
240+
},
241+
});
242+
```
243+
244+
### Add `language` support to `createCustomerAccountClient` and `createHydrogenContext` [#2746](https://github.com/Shopify/hydrogen/pull/2746)
245+
246+
#### Step: 1. If present, the provided `language` will be used to set the `uilocales` property in the Customer Account API request. This will allow the API to return localized data for the provided language. [#2746](https://github.com/Shopify/hydrogen/pull/2746)
247+
248+
[#2746](https://github.com/Shopify/hydrogen/pull/2746)
249+
```ts
250+
// Optional: provide language data to the constructor
251+
const customerAccount = createCustomerAccountClient({
252+
// ...
253+
language,
254+
});
255+
```
256+
257+
#### Step: 2. Calls to `login()` will use the provided `language` without having to pass it explicitly via `uiLocales`; however, if the `login()` method is already using its `uilocales` property, the `language` parameter coming from the context/constructor will be ignored. If nothing is explicitly passed, `login()` will default to `context.i18n.language`. [#2746](https://github.com/Shopify/hydrogen/pull/2746)
258+
259+
[#2746](https://github.com/Shopify/hydrogen/pull/2746)
260+
```ts
261+
export async function loader({request, context}: LoaderFunctionArgs) {
262+
return context.customerAccount.login({
263+
uiLocales: 'FR', // will be used instead of the one coming from the context
264+
});
265+
}
266+
```
267+
268+
----

app/entry.server.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default async function handleRequest(
2323
});
2424
const body = await renderToReadableStream(
2525
<NonceProvider>
26-
<RemixServer context={remixContext} url={request.url} />
26+
<RemixServer context={remixContext} url={request.url} nonce={nonce} />
2727
</NonceProvider>,
2828
{
2929
nonce,

app/root.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
type LoaderFunctionArgs,
2424
type MetaArgs,
2525
type SerializeFrom,
26-
defer,
2726
} from "@shopify/remix-oxygen";
2827
import { withWeaverse } from "@weaverse/hydrogen";
2928
import tailwind from "./styles/tailwind.css?url";
@@ -90,10 +89,10 @@ export async function loader(args: LoaderFunctionArgs) {
9089
// Await the critical data required to render initial state of the page
9190
const criticalData = await loadCriticalData(args);
9291

93-
return defer({
92+
return {
9493
...deferredData,
9594
...criticalData,
96-
});
95+
};
9796
}
9897

9998
/**

app/routes/($locale)._index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {type MetaFunction} from '@remix-run/react';
22
import {getSeoMeta, type SeoConfig} from '@shopify/hydrogen';
3-
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
3+
import { type LoaderFunctionArgs } from '@shopify/remix-oxygen';
44
import {seoPayload} from '~/lib/seo.server';
55
import {WeaverseContent} from '~/weaverse';
66

@@ -11,11 +11,11 @@ export async function loader({context}: LoaderFunctionArgs) {
1111
);
1212
let seo = seoPayload.home();
1313

14-
return defer({
14+
return {
1515
recommendedProducts,
1616
weaverseData: await context.weaverse.loadPage(),
1717
seo,
18-
});
18+
};
1919
}
2020

2121
export const meta: MetaFunction<typeof loader> = ({data}) => {

app/routes/($locale).account.orders.$id.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1+
import { data, redirect, type LoaderFunctionArgs } from '@shopify/remix-oxygen';
22
import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
33
import {Money, Image, flattenConnection} from '@shopify/hydrogen';
44
import type {OrderLineItemFullFragment} from 'customer-accountapi.generated';
@@ -14,18 +14,18 @@ export async function loader({params, context, request}: LoaderFunctionArgs) {
1414
}
1515

1616
const orderId = atob(params.id);
17-
const {data, errors} = await context.customerAccount.query(
17+
const { data: orderData, errors } = await context.customerAccount.query(
1818
CUSTOMER_ORDER_QUERY,
1919
{
2020
variables: {orderId},
2121
},
2222
);
2323

24-
if (errors?.length || !data?.order) {
24+
if (errors?.length || !orderData?.order) {
2525
throw new Error('Order not found');
2626
}
2727

28-
const {order} = data;
28+
const { order } = orderData;
2929

3030
const lineItems = flattenConnection(order.lineItems);
3131
const discountApplications = flattenConnection(order.discountApplications);
@@ -40,7 +40,7 @@ export async function loader({params, context, request}: LoaderFunctionArgs) {
4040
firstDiscount?.__typename === 'PricingPercentageValue' &&
4141
firstDiscount?.percentage;
4242

43-
return json(
43+
return data(
4444
{
4545
order,
4646
lineItems,

app/routes/($locale).api.countries.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import {json} from '@shopify/remix-oxygen';
1+
import { data } from '@shopify/remix-oxygen';
22

33
import {CACHE_LONG} from '~/data/cache';
44
import {countries} from '~/data/countries';
55

66
export async function loader() {
7-
return json(
7+
return data(
88
{
99
...countries,
1010
},

app/routes/($locale).api.predictive-search.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
1+
import { data, type LoaderFunctionArgs } from '@shopify/remix-oxygen';
22

33
import type {
44
PredictiveArticleFragment,
@@ -46,7 +46,7 @@ export async function action({request, params, context}: LoaderFunctionArgs) {
4646
context,
4747
});
4848

49-
return json(search);
49+
return data(search);
5050
}
5151

5252
async function fetchPredictiveSearchResults({

0 commit comments

Comments
 (0)