diff --git a/.changeset/plenty-cycles-act.md b/.changeset/plenty-cycles-act.md new file mode 100644 index 0000000000..21521ce6b7 --- /dev/null +++ b/.changeset/plenty-cycles-act.md @@ -0,0 +1,95 @@ +--- +'skeleton': patch +--- + +[**Breaking change**] + +Turn on Remix `v3_singleFetch` future flag + +Remix single fetch migration quick guide: https://remix.run/docs/en/main/start/future-flags#v3_singlefetch +Remix single fetch migration guide: https://remix.run/docs/en/main/guides/single-fetch + +**Note:** If you have any routes that appends (or looks for) a search param named `_data`, make sure to rename it to something else. + +1. In your `vite.config.ts`, add the single fetch future flag. + + ```diff + + declare module "@remix-run/server-runtime" { + + interface Future { + + v3_singleFetch: true; + + } + + } + + export default defineConfig({ + plugins: [ + hydrogen(), + oxygen(), + remix({ + presets: [hydrogen.preset()], + future: { + v3_fetcherPersist: true, + v3_relativeSplatPath: true, + v3_throwAbortReason: true, + v3_lazyRouteDiscovery: true, + + v3_singleFetch: true, + }, + }), + tsconfigPaths(), + ], + ``` + +2. In your `entry.server.tsx`, add `nonce` to the ``. + + ```diff + const body = await renderToReadableStream( + + + , + ``` +3. Deprecate `json` and `defer` import usage from `@shopify/remix-oxygen`. + + Remove `json()`/`defer()` in favor of raw objects. + + Single Fetch supports JSON objects and Promises out of the box, so you can return the raw data from your loader/action functions: + + ```diff + - import {json} from "@shopify/remix-oxygen"; + + export async function loader({}: LoaderFunctionArgs) { + let tasks = await fetchTasks(); + - return json(tasks); + + return tasks; + } + ``` + + ```diff + - import {defer} from "@shopify/remix-oxygen"; + + export async function loader({}: LoaderFunctionArgs) { + let lazyStuff = fetchLazyStuff(); + let tasks = await fetchTasks(); + - return defer({ tasks, lazyStuff }); + + return { tasks, lazyStuff }; + } + ``` + + 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: + + ```diff + - import {json} from "@shopify/remix-oxygen"; + + import {data} from "@shopify/remix-oxygen"; + + export async function loader({}: LoaderFunctionArgs) { + let tasks = await fetchTasks(); + - return json(tasks, { + + return data(tasks, { + headers: { + "Cache-Control": "public, max-age=604800" + } + }); + } + ``` diff --git a/.changeset/purple-buses-laugh.md b/.changeset/purple-buses-laugh.md new file mode 100644 index 0000000000..f1b9a54a92 --- /dev/null +++ b/.changeset/purple-buses-laugh.md @@ -0,0 +1,7 @@ +--- +'@shopify/remix-oxygen': patch +'@shopify/hydrogen': patch +'@shopify/cli-hydrogen': patch +--- + +Turn on Remix `v3_singleFetch` future flag diff --git a/docs/preview/app/root.tsx b/docs/preview/app/root.tsx index 5469d77c49..47446f0d23 100644 --- a/docs/preview/app/root.tsx +++ b/docs/preview/app/root.tsx @@ -1,4 +1,4 @@ -import {json, type LinksFunction} from '@remix-run/node'; +import {type LinksFunction} from '@remix-run/node'; import { Links, Meta, @@ -26,9 +26,7 @@ export async function loader() { } } - return json({ - data, - }); + return {data}; } export default function App() { diff --git a/docs/preview/app/routes/$doc.tsx b/docs/preview/app/routes/$doc.tsx index bf3cc745d6..3bc96aac42 100644 --- a/docs/preview/app/routes/$doc.tsx +++ b/docs/preview/app/routes/$doc.tsx @@ -9,7 +9,7 @@ import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'; import {oneDark} from 'react-syntax-highlighter/dist/cjs/styles/prism/index.js'; export async function loader({params}: LoaderFunctionArgs) { - return json({doc: params.doc}); + return {doc: params.doc}; } function getDefinition(definitions: any, type: string) { diff --git a/docs/preview/vite.config.js b/docs/preview/vite.config.js index 4510819d7f..3cabcc28c3 100644 --- a/docs/preview/vite.config.js +++ b/docs/preview/vite.config.js @@ -23,6 +23,7 @@ export default defineConfig({ v3_fetcherPersist: false, v3_relativeSplatPath: false, v3_throwAbortReason: false, + v3_singleFetch: true, }, }), tsconfigPaths(), diff --git a/docs/shopify-dev/analytics-setup/js/app/entry-server.jsx b/docs/shopify-dev/analytics-setup/js/app/entry-server.jsx index 704df4239b..48777e7022 100644 --- a/docs/shopify-dev/analytics-setup/js/app/entry-server.jsx +++ b/docs/shopify-dev/analytics-setup/js/app/entry-server.jsx @@ -30,7 +30,7 @@ export default async function handleRequest( const body = await renderToReadableStream( - + , { nonce, diff --git a/docs/shopify-dev/analytics-setup/js/app/root.jsx b/docs/shopify-dev/analytics-setup/js/app/root.jsx index 588d41593d..15fc7d3a9c 100644 --- a/docs/shopify-dev/analytics-setup/js/app/root.jsx +++ b/docs/shopify-dev/analytics-setup/js/app/root.jsx @@ -5,7 +5,6 @@ import { Analytics, // [END import] } from '@shopify/hydrogen'; -import {defer} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -83,7 +82,7 @@ export async function loader(args) { const {storefront, env} = args.context; // [END env] - return defer({ + return { ...deferredData, ...criticalData, publicStoreDomain: env.PUBLIC_STORE_DOMAIN, @@ -103,7 +102,7 @@ export async function loader(args) { language: args.context.storefront.i18n.language, }, // [END consent] - }); + }; } /** diff --git a/docs/shopify-dev/analytics-setup/js/app/routes/cart.jsx b/docs/shopify-dev/analytics-setup/js/app/routes/cart.jsx index c7381487d6..6cb2c6fbd8 100644 --- a/docs/shopify-dev/analytics-setup/js/app/routes/cart.jsx +++ b/docs/shopify-dev/analytics-setup/js/app/routes/cart.jsx @@ -1,6 +1,6 @@ import {useLoaderData} from '@remix-run/react'; import {CartForm, Analytics} from '@shopify/hydrogen'; -import {json} from '@shopify/remix-oxygen'; +import {data} from '@shopify/remix-oxygen'; import {CartMain} from '~/components/CartMain'; /** @@ -81,7 +81,7 @@ export async function action({request, context}) { headers.set('Location', redirectTo); } - return json( + return data( { cart: cartResult, errors, @@ -99,7 +99,7 @@ export async function action({request, context}) { */ export async function loader({context}) { const {cart} = context; - return json(await cart.get()); + return await cart.get(); } export default function Cart() { diff --git a/docs/shopify-dev/analytics-setup/js/app/routes/collections.$handle.jsx b/docs/shopify-dev/analytics-setup/js/app/routes/collections.$handle.jsx index 9360370805..e526fe84e5 100644 --- a/docs/shopify-dev/analytics-setup/js/app/routes/collections.$handle.jsx +++ b/docs/shopify-dev/analytics-setup/js/app/routes/collections.$handle.jsx @@ -26,7 +26,7 @@ export async function loader(args) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/docs/shopify-dev/analytics-setup/js/app/routes/products.$handle.jsx b/docs/shopify-dev/analytics-setup/js/app/routes/products.$handle.jsx index 606ee07e66..cf1a37f196 100644 --- a/docs/shopify-dev/analytics-setup/js/app/routes/products.$handle.jsx +++ b/docs/shopify-dev/analytics-setup/js/app/routes/products.$handle.jsx @@ -32,7 +32,7 @@ export async function loader(args) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/docs/shopify-dev/analytics-setup/js/app/routes/search.jsx b/docs/shopify-dev/analytics-setup/js/app/routes/search.jsx index b20ffc3820..b2dd0ed9f7 100644 --- a/docs/shopify-dev/analytics-setup/js/app/routes/search.jsx +++ b/docs/shopify-dev/analytics-setup/js/app/routes/search.jsx @@ -1,4 +1,3 @@ -import {json} from '@shopify/remix-oxygen'; import {useLoaderData} from '@remix-run/react'; import {getPaginationVariables, Analytics} from '@shopify/hydrogen'; import {SearchForm} from '~/components/SearchForm'; @@ -27,7 +26,7 @@ export async function loader({request, context}) { return {term: '', result: null, error: error.message}; }); - return json(await searchPromise); + return await searchPromise; } /** diff --git a/docs/shopify-dev/analytics-setup/ts/app/entry-server.tsx b/docs/shopify-dev/analytics-setup/ts/app/entry-server.tsx index 3eb183a28a..1f36128659 100644 --- a/docs/shopify-dev/analytics-setup/ts/app/entry-server.tsx +++ b/docs/shopify-dev/analytics-setup/ts/app/entry-server.tsx @@ -29,7 +29,7 @@ export default async function handleRequest( const body = await renderToReadableStream( - + , { nonce, diff --git a/docs/shopify-dev/analytics-setup/ts/app/root.tsx b/docs/shopify-dev/analytics-setup/ts/app/root.tsx index b3a43d75ab..868cde5c65 100644 --- a/docs/shopify-dev/analytics-setup/ts/app/root.tsx +++ b/docs/shopify-dev/analytics-setup/ts/app/root.tsx @@ -5,7 +5,7 @@ import { Analytics, // [END import] } from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -83,7 +83,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({ + return { ...deferredData, ...criticalData, publicStoreDomain: env.PUBLIC_STORE_DOMAIN, @@ -103,7 +103,7 @@ export async function loader(args: LoaderFunctionArgs) { language: storefront.i18n.language, }, // [END consent] - }); + }; } /** diff --git a/docs/shopify-dev/analytics-setup/ts/app/routes/cart.tsx b/docs/shopify-dev/analytics-setup/ts/app/routes/cart.tsx index 0789789ad6..8d2dffc482 100644 --- a/docs/shopify-dev/analytics-setup/ts/app/routes/cart.tsx +++ b/docs/shopify-dev/analytics-setup/ts/app/routes/cart.tsx @@ -1,7 +1,7 @@ import {type MetaFunction, useLoaderData} from '@remix-run/react'; import type {CartQueryDataReturn} from '@shopify/hydrogen'; import {CartForm, Analytics} from '@shopify/hydrogen'; -import {json, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; +import {data, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; import {CartMain} from '~/components/CartMain'; export const meta: MetaFunction = () => { @@ -80,7 +80,7 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - return json( + return data( { cart: cartResult, errors, @@ -95,7 +95,7 @@ export async function action({request, context}: ActionFunctionArgs) { export async function loader({context}: LoaderFunctionArgs) { const {cart} = context; - return json(await cart.get()); + return await cart.get(); } export default function Cart() { diff --git a/docs/shopify-dev/analytics-setup/ts/app/routes/collections.$handle.tsx b/docs/shopify-dev/analytics-setup/ts/app/routes/collections.$handle.tsx index 1e1b4c9171..086056ed0c 100644 --- a/docs/shopify-dev/analytics-setup/ts/app/routes/collections.$handle.tsx +++ b/docs/shopify-dev/analytics-setup/ts/app/routes/collections.$handle.tsx @@ -1,4 +1,4 @@ -import {defer, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, Link, type MetaFunction} from '@remix-run/react'; import { getPaginationVariables, @@ -21,7 +21,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/docs/shopify-dev/analytics-setup/ts/app/routes/products.$handle.tsx b/docs/shopify-dev/analytics-setup/ts/app/routes/products.$handle.tsx index fbd644dc85..918bdc8b0c 100644 --- a/docs/shopify-dev/analytics-setup/ts/app/routes/products.$handle.tsx +++ b/docs/shopify-dev/analytics-setup/ts/app/routes/products.$handle.tsx @@ -1,6 +1,5 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; -import type {ProductFragment} from 'storefrontapi.generated'; import { getSelectedProductOptions, // [START import] @@ -11,8 +10,6 @@ import { getAdjacentAndFirstAvailableVariants, useSelectedOptionInUrlParam, } from '@shopify/hydrogen'; -import type {SelectedOption} from '@shopify/hydrogen/storefront-api-types'; -import {getVariantUrl} from '~/lib/variants'; import {ProductPrice} from '~/components/ProductPrice'; import {ProductImage} from '~/components/ProductImage'; import {ProductForm} from '~/components/ProductForm'; @@ -28,7 +25,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/docs/shopify-dev/analytics-setup/ts/app/routes/search.tsx b/docs/shopify-dev/analytics-setup/ts/app/routes/search.tsx index 01e6009f38..f3e6fcbc15 100644 --- a/docs/shopify-dev/analytics-setup/ts/app/routes/search.tsx +++ b/docs/shopify-dev/analytics-setup/ts/app/routes/search.tsx @@ -1,5 +1,4 @@ import { - json, type LoaderFunctionArgs, type ActionFunctionArgs, } from '@shopify/remix-oxygen'; @@ -29,7 +28,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { return {term: '', result: null, error: error.message}; }); - return json(await searchPromise); + return await searchPromise; } /** diff --git a/examples/b2b/app/root.tsx b/examples/b2b/app/root.tsx index 41515dda50..6c86314bd9 100644 --- a/examples/b2b/app/root.tsx +++ b/examples/b2b/app/root.tsx @@ -1,5 +1,5 @@ import {useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -119,7 +119,7 @@ export async function loader({context}: LoaderFunctionArgs) { }, }); - return defer({ + return { cart: cartPromise, footer: footerPromise, header: await headerPromise, @@ -137,7 +137,7 @@ export async function loader({context}: LoaderFunctionArgs) { country: context.storefront.i18n.country, language: context.storefront.i18n.language, }, - }); + }; } export function Layout({children}: {children?: React.ReactNode}) { diff --git a/examples/b2b/app/routes/b2blocations.tsx b/examples/b2b/app/routes/b2blocations.tsx index 6cda28b53a..04a572a785 100644 --- a/examples/b2b/app/routes/b2blocations.tsx +++ b/examples/b2b/app/routes/b2blocations.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData} from '@remix-run/react'; import {B2BLocationSelector} from '../components/B2BLocationSelector'; import {CUSTOMER_LOCATIONS_QUERY} from '~/graphql/customer-account/CustomerLocationsQuery'; @@ -30,7 +30,7 @@ export async function loader({context}: LoaderFunctionArgs) { const modalOpen = Boolean(company) && !companyLocationId; - return defer({company, companyLocationId, modalOpen}); + return {company, companyLocationId, modalOpen}; } export default function CartRoute() { diff --git a/examples/b2b/app/routes/products.$handle.tsx b/examples/b2b/app/routes/products.$handle.tsx index 02f350ea5c..65bb5df85c 100644 --- a/examples/b2b/app/routes/products.$handle.tsx +++ b/examples/b2b/app/routes/products.$handle.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; import { getSelectedProductOptions, @@ -53,7 +53,7 @@ export async function loader(args: LoaderFunctionArgs) { /********** EXAMPLE UPDATE END *************/ /***********************************************/ - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/examples/b2b/tsconfig.json b/examples/b2b/tsconfig.json index 5b672cc6e1..c54afd12a4 100644 --- a/examples/b2b/tsconfig.json +++ b/examples/b2b/tsconfig.json @@ -4,7 +4,8 @@ "./**/*.d.ts", "./**/*.ts", "./**/*.tsx", - "../../templates/skeleton/*.d.ts" + "../../templates/skeleton/*.d.ts", + "../../templates/skeleton/vite.config.ts" ], "compilerOptions": { "baseUrl": ".", diff --git a/examples/classic-remix/app/root.tsx b/examples/classic-remix/app/root.tsx index 832c78a7ff..5344691318 100644 --- a/examples/classic-remix/app/root.tsx +++ b/examples/classic-remix/app/root.tsx @@ -1,5 +1,5 @@ import {useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -72,7 +72,7 @@ export async function loader(args: LoaderFunctionArgs) { const {storefront, env} = args.context; - return defer({ + return { ...criticalData, ...deferredData, publicStoreDomain: env.PUBLIC_STORE_DOMAIN, @@ -84,7 +84,7 @@ export async function loader(args: LoaderFunctionArgs) { checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, }, - }); + }; } /** diff --git a/examples/classic-remix/remix.config.js b/examples/classic-remix/remix.config.js index 73250a97f9..81dcaf80cf 100644 --- a/examples/classic-remix/remix.config.js +++ b/examples/classic-remix/remix.config.js @@ -21,5 +21,6 @@ module.exports = { v3_relativeSplatpath: true, v3_throwAbortReason: true, v3_lazyRouteDiscovery: true, + v3_singleFetch: true, }, }; diff --git a/examples/classic-remix/tsconfig.json b/examples/classic-remix/tsconfig.json index 110d781eea..3bf5db6c41 100644 --- a/examples/classic-remix/tsconfig.json +++ b/examples/classic-remix/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../templates/skeleton/tsconfig.json", - "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"], + "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "../../templates/skeleton/vite.config.ts"], "compilerOptions": { "baseUrl": ".", "paths": { diff --git a/examples/custom-cart-method/app/routes/cart.tsx b/examples/custom-cart-method/app/routes/cart.tsx index ed003875bb..4d0057f865 100644 --- a/examples/custom-cart-method/app/routes/cart.tsx +++ b/examples/custom-cart-method/app/routes/cart.tsx @@ -1,7 +1,7 @@ import {type MetaFunction, useLoaderData} from '@remix-run/react'; import type {CartQueryDataReturn} from '@shopify/hydrogen'; import {CartForm} from '@shopify/hydrogen'; -import {json, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; +import {data, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; import type { SelectedOptionInput, CartLineUpdateInput, @@ -103,7 +103,7 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - return json( + return data( { cart: cartResult, errors, @@ -117,7 +117,7 @@ export async function action({request, context}: ActionFunctionArgs) { export async function loader({context}: LoaderFunctionArgs) { const {cart} = context; - return json(await cart.get()); + return await cart.get(); } export default function Cart() { diff --git a/examples/custom-cart-method/tsconfig.json b/examples/custom-cart-method/tsconfig.json index 5b672cc6e1..c54afd12a4 100644 --- a/examples/custom-cart-method/tsconfig.json +++ b/examples/custom-cart-method/tsconfig.json @@ -4,7 +4,8 @@ "./**/*.d.ts", "./**/*.ts", "./**/*.tsx", - "../../templates/skeleton/*.d.ts" + "../../templates/skeleton/*.d.ts", + "../../templates/skeleton/vite.config.ts" ], "compilerOptions": { "baseUrl": ".", diff --git a/examples/express/app/entry.server.tsx b/examples/express/app/entry.server.tsx index 3f3cfef03b..bef2bbb62f 100644 --- a/examples/express/app/entry.server.tsx +++ b/examples/express/app/entry.server.tsx @@ -59,6 +59,7 @@ function handleBotRequest( context={remixContext} url={request.url} abortDelay={ABORT_DELAY} + nonce={nonce} /> , { @@ -109,6 +110,7 @@ function handleBrowserRequest( context={remixContext} url={request.url} abortDelay={ABORT_DELAY} + nonce={nonce} /> , { diff --git a/examples/express/app/root.tsx b/examples/express/app/root.tsx index 122a44f0a0..76d53b0005 100644 --- a/examples/express/app/root.tsx +++ b/examples/express/app/root.tsx @@ -1,5 +1,4 @@ import { - defer, type LinksFunction, type LoaderFunctionArgs, } from '@remix-run/node'; @@ -66,11 +65,11 @@ export async function loader({context}: LoaderFunctionArgs) { await context.storefront.query<{shop: Shop}>(LAYOUT_QUERY), ]); - return defer({ + return { isLoggedIn: Boolean(customerAccessToken), cart, layout, - }); + }; } export function Layout({children}: {children?: React.ReactNode}) { diff --git a/examples/express/app/routes/products.$handle.tsx b/examples/express/app/routes/products.$handle.tsx index b50323fd9f..5264bb44a0 100644 --- a/examples/express/app/routes/products.$handle.tsx +++ b/examples/express/app/routes/products.$handle.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@remix-run/node'; +import {type LoaderFunctionArgs} from '@remix-run/node'; import {useLoaderData} from '@remix-run/react'; export async function loader({params, context}: LoaderFunctionArgs) { @@ -25,7 +25,7 @@ export async function loader({params, context}: LoaderFunctionArgs) { throw new Response(null, {status: 404}); } - return json({product}); + return {product}; } export default function Product() { diff --git a/examples/express/vite.config.ts b/examples/express/vite.config.ts index b24f88644c..203db64983 100644 --- a/examples/express/vite.config.ts +++ b/examples/express/vite.config.ts @@ -13,6 +13,7 @@ export default defineConfig({ v3_relativeSplatPath: true, v3_throwAbortReason: true, v3_lazyRouteDiscovery: true, + v3_singleFetch: true, }, }), tsconfigPaths(), diff --git a/examples/gtm/app/entry.server.tsx b/examples/gtm/app/entry.server.tsx index 389576f0f4..dcb1cf925e 100644 --- a/examples/gtm/app/entry.server.tsx +++ b/examples/gtm/app/entry.server.tsx @@ -37,7 +37,7 @@ export default async function handleRequest( const body = await renderToReadableStream( - + , { nonce, diff --git a/examples/gtm/app/root.tsx b/examples/gtm/app/root.tsx index 86898d0e59..141c003042 100644 --- a/examples/gtm/app/root.tsx +++ b/examples/gtm/app/root.tsx @@ -1,5 +1,5 @@ import {useNonce, getShopAnalytics, Analytics, Script} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -74,7 +74,7 @@ export async function loader(args: LoaderFunctionArgs) { const {storefront, env} = args.context; - return defer({ + return { ...deferredData, ...criticalData, publicStoreDomain: env.PUBLIC_STORE_DOMAIN, @@ -90,7 +90,7 @@ export async function loader(args: LoaderFunctionArgs) { country: args.context.storefront.i18n.country, language: args.context.storefront.i18n.language, }, - }); + }; } /** diff --git a/examples/gtm/tsconfig.json b/examples/gtm/tsconfig.json index 5b672cc6e1..c54afd12a4 100644 --- a/examples/gtm/tsconfig.json +++ b/examples/gtm/tsconfig.json @@ -4,7 +4,8 @@ "./**/*.d.ts", "./**/*.ts", "./**/*.tsx", - "../../templates/skeleton/*.d.ts" + "../../templates/skeleton/*.d.ts", + "../../templates/skeleton/vite.config.ts" ], "compilerOptions": { "baseUrl": ".", diff --git a/examples/infinite-scroll/app/routes/collections.$handle.tsx b/examples/infinite-scroll/app/routes/collections.$handle.tsx index 66186a8ba8..cffe926e56 100644 --- a/examples/infinite-scroll/app/routes/collections.$handle.tsx +++ b/examples/infinite-scroll/app/routes/collections.$handle.tsx @@ -1,4 +1,4 @@ -import {defer, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { useLoaderData, useNavigate, @@ -28,7 +28,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/examples/infinite-scroll/tsconfig.json b/examples/infinite-scroll/tsconfig.json index ad451d25b6..c54afd12a4 100644 --- a/examples/infinite-scroll/tsconfig.json +++ b/examples/infinite-scroll/tsconfig.json @@ -1,6 +1,12 @@ { "extends": "../../templates/skeleton/tsconfig.json", - "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "../../templates/skeleton/*.d.ts"], + "include": [ + "./**/*.d.ts", + "./**/*.ts", + "./**/*.tsx", + "../../templates/skeleton/*.d.ts", + "../../templates/skeleton/vite.config.ts" + ], "compilerOptions": { "baseUrl": ".", "paths": { diff --git a/examples/legacy-customer-account-flow/app/root.tsx b/examples/legacy-customer-account-flow/app/root.tsx index 8b13edb28e..cc3b6ef801 100644 --- a/examples/legacy-customer-account-flow/app/root.tsx +++ b/examples/legacy-customer-account-flow/app/root.tsx @@ -1,5 +1,5 @@ import {useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {data, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -99,7 +99,7 @@ export async function loader({context}: LoaderFunctionArgs) { }, }); - return defer( + return data( { cart: cartPromise, footer: footerPromise, diff --git a/examples/legacy-customer-account-flow/app/routes/account.addresses.tsx b/examples/legacy-customer-account-flow/app/routes/account.addresses.tsx index 23ff85b0df..76ae8b40ae 100644 --- a/examples/legacy-customer-account-flow/app/routes/account.addresses.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account.addresses.tsx @@ -1,7 +1,7 @@ import type {MailingAddressInput} from '@shopify/hydrogen/storefront-api-types'; import type {AddressFragment, CustomerFragment} from 'storefrontapi.generated'; import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -33,7 +33,7 @@ export async function loader({context}: LoaderFunctionArgs) { if (!customerAccessToken) { return redirect('/account/login'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { @@ -51,7 +51,7 @@ export async function action({request, context}: ActionFunctionArgs) { const customerAccessToken = await session.get('customerAccessToken'); if (!customerAccessToken) { - return json({error: {[addressId]: 'Unauthorized'}}, {status: 401}); + return data({error: {[addressId]: 'Unauthorized'}}, {status: 401}); } const {accessToken} = customerAccessToken; @@ -120,12 +120,12 @@ export async function action({request, context}: ActionFunctionArgs) { } } - return json({error: null, createdAddress, defaultAddress}); + return {error: null, createdAddress, defaultAddress}; } catch (error: unknown) { if (error instanceof Error) { - return json({error: {[addressId]: error.message}}, {status: 400}); + return data({error: {[addressId]: error.message}}, {status: 400}); } - return json({error: {[addressId]: error}}, {status: 400}); + return data({error: {[addressId]: error}}, {status: 400}); } } @@ -167,12 +167,12 @@ export async function action({request, context}: ActionFunctionArgs) { } } - return json({error: null, updatedAddress, defaultAddress}); + return {error: null, updatedAddress, defaultAddress}; } catch (error: unknown) { if (error instanceof Error) { - return json({error: {[addressId]: error.message}}, {status: 400}); + return data({error: {[addressId]: error.message}}, {status: 400}); } - return json({error: {[addressId]: error}}, {status: 400}); + return data({error: {[addressId]: error}}, {status: 400}); } } @@ -190,17 +190,17 @@ export async function action({request, context}: ActionFunctionArgs) { const error = customerAddressDelete.customerUserErrors[0]; throw new Error(error.message); } - return json({error: null, deletedAddress: addressId}); + return {error: null, deletedAddress: addressId}; } catch (error: unknown) { if (error instanceof Error) { - return json({error: {[addressId]: error.message}}, {status: 400}); + return data({error: {[addressId]: error.message}}, {status: 400}); } - return json({error: {[addressId]: error}}, {status: 400}); + return data({error: {[addressId]: error}}, {status: 400}); } } default: { - return json( + return data( {error: {[addressId]: 'Method not allowed'}}, {status: 405}, ); @@ -208,9 +208,9 @@ export async function action({request, context}: ActionFunctionArgs) { } } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/legacy-customer-account-flow/app/routes/account.orders.$id.tsx b/examples/legacy-customer-account-flow/app/routes/account.orders.$id.tsx index 9d4feb4a4f..932ee97af9 100644 --- a/examples/legacy-customer-account-flow/app/routes/account.orders.$id.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account.orders.$id.tsx @@ -1,4 +1,4 @@ -import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import {Money, Image, flattenConnection} from '@shopify/hydrogen'; import type {OrderLineItemFullFragment} from 'storefrontapi.generated'; @@ -41,12 +41,12 @@ export async function loader({params, context}: LoaderFunctionArgs) { firstDiscount?.__typename === 'PricingPercentageValue' && firstDiscount?.percentage; - return json({ + return { order, lineItems, discountValue, discountPercentage, - }); + }; } export default function OrderRoute() { diff --git a/examples/legacy-customer-account-flow/app/routes/account.orders._index.tsx b/examples/legacy-customer-account-flow/app/routes/account.orders._index.tsx index 4604fb568d..d5bda5f2f3 100644 --- a/examples/legacy-customer-account-flow/app/routes/account.orders._index.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account.orders._index.tsx @@ -1,6 +1,6 @@ import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import {Money, Pagination, getPaginationVariables} from '@shopify/hydrogen'; -import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {data, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import type { CustomerOrdersFragment, OrderItemFragment, @@ -37,12 +37,12 @@ export async function loader({request, context}: LoaderFunctionArgs) { throw new Error('Customer not found'); } - return json({customer}); + return {customer}; } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/legacy-customer-account-flow/app/routes/account.profile.tsx b/examples/legacy-customer-account-flow/app/routes/account.profile.tsx index 3edf686d8a..b1a834b302 100644 --- a/examples/legacy-customer-account-flow/app/routes/account.profile.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account.profile.tsx @@ -1,7 +1,7 @@ import type {CustomerFragment} from 'storefrontapi.generated'; import type {CustomerUpdateInput} from '@shopify/hydrogen/storefront-api-types'; import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -28,20 +28,20 @@ export async function loader({context}: LoaderFunctionArgs) { if (!customerAccessToken) { return redirect('/account/login'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { const {session, storefront} = context; if (request.method !== 'PUT') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } const form = await request.formData(); const customerAccessToken = await session.get('customerAccessToken'); if (!customerAccessToken) { - return json({error: 'Unauthorized'}, {status: 401}); + return data({error: 'Unauthorized'}, {status: 401}); } try { @@ -80,7 +80,7 @@ export async function action({request, context}: ActionFunctionArgs) { // check for mutation errors if (updated.customerUpdate?.customerUserErrors?.length) { - return json( + return data( {error: updated.customerUpdate?.customerUserErrors[0]}, {status: 400}, ); @@ -94,9 +94,9 @@ export async function action({request, context}: ActionFunctionArgs) { ); } - return json({error: null, customer: updated.customerUpdate?.customer}); + return {error: null, customer: updated.customerUpdate?.customer}; } catch (error: any) { - return json({error: error.message, customer: null}, {status: 400}); + return data({error: error.message, customer: null}, {status: 400}); } } diff --git a/examples/legacy-customer-account-flow/app/routes/account.tsx b/examples/legacy-customer-account-flow/app/routes/account.tsx index 913cd65a93..6547c07f92 100644 --- a/examples/legacy-customer-account-flow/app/routes/account.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account.tsx @@ -1,5 +1,5 @@ import {Form, NavLink, Outlet, useLoaderData} from '@remix-run/react'; -import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {data, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import type {CustomerFragment} from 'storefrontapi.generated'; export function shouldRevalidate() { @@ -23,12 +23,12 @@ export async function loader({request, context}: LoaderFunctionArgs) { return redirect('/account/login'); } else { // public subroute such as /account/login... - return json({ + return { isLoggedIn: false, isAccountHome, isPrivateRoute, customer: null, - }); + }; } } else { // loggedIn, default redirect to the orders page @@ -51,7 +51,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { throw new Error('Customer not found'); } - return json( + return data( {isLoggedIn, isPrivateRoute, isAccountHome, customer}, { headers: { diff --git a/examples/legacy-customer-account-flow/app/routes/account_.activate.$id.$activationToken.tsx b/examples/legacy-customer-account-flow/app/routes/account_.activate.$id.$activationToken.tsx index 49d1c4106e..cd7f55ba94 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.activate.$id.$activationToken.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.activate.$id.$activationToken.tsx @@ -1,5 +1,5 @@ import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -18,7 +18,7 @@ export async function loader({context}: LoaderFunctionArgs) { if (await context.session.get('customerAccessToken')) { return redirect('/account'); } - return json({}); + return {}; } export async function action({request, context, params}: ActionFunctionArgs) { @@ -26,7 +26,7 @@ export async function action({request, context, params}: ActionFunctionArgs) { const {id, activationToken} = params; if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } try { @@ -73,9 +73,9 @@ export async function action({request, context, params}: ActionFunctionArgs) { return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/legacy-customer-account-flow/app/routes/account_.login.tsx b/examples/legacy-customer-account-flow/app/routes/account_.login.tsx index f3537f8ae2..fc3564188c 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.login.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.login.tsx @@ -1,5 +1,5 @@ import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -18,14 +18,14 @@ export async function loader({context}: LoaderFunctionArgs) { if (await context.session.get('customerAccessToken')) { return redirect('/account'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { const {session, storefront} = context; if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } try { @@ -57,9 +57,9 @@ export async function action({request, context}: ActionFunctionArgs) { return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/legacy-customer-account-flow/app/routes/account_.logout.tsx b/examples/legacy-customer-account-flow/app/routes/account_.logout.tsx index 8981fc3993..31a41bc501 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.logout.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.logout.tsx @@ -1,4 +1,4 @@ -import {json, redirect, type ActionFunctionArgs} from '@shopify/remix-oxygen'; +import {data, redirect, type ActionFunctionArgs} from '@shopify/remix-oxygen'; import {type MetaFunction} from '@remix-run/react'; export const meta: MetaFunction = () => { @@ -14,7 +14,7 @@ export async function action({request, context}: ActionFunctionArgs) { session.unset('customerAccessToken'); if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } return redirect('/'); diff --git a/examples/legacy-customer-account-flow/app/routes/account_.recover.tsx b/examples/legacy-customer-account-flow/app/routes/account_.recover.tsx index 25a20fea91..aded9d8530 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.recover.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.recover.tsx @@ -1,5 +1,5 @@ import { - json, + data, redirect, type LoaderFunctionArgs, type ActionFunctionArgs, @@ -17,7 +17,7 @@ export async function loader({context}: LoaderFunctionArgs) { return redirect('/account'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { @@ -26,7 +26,7 @@ export async function action({request, context}: ActionFunctionArgs) { const email = form.has('email') ? String(form.get('email')) : null; if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } try { @@ -37,13 +37,13 @@ export async function action({request, context}: ActionFunctionArgs) { variables: {email}, }); - return json({resetRequested: true}); + return {resetRequested: true}; } catch (error: unknown) { const resetRequested = false; if (error instanceof Error) { - return json({error: error.message, resetRequested}, {status: 400}); + return data({error: error.message, resetRequested}, {status: 400}); } - return json({error, resetRequested}, {status: 400}); + return data({error, resetRequested}, {status: 400}); } } diff --git a/examples/legacy-customer-account-flow/app/routes/account_.register.tsx b/examples/legacy-customer-account-flow/app/routes/account_.register.tsx index 2bfa62a39e..64c8a104ee 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.register.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.register.tsx @@ -1,5 +1,5 @@ import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -20,12 +20,12 @@ export async function loader({context}: LoaderFunctionArgs) { return redirect('/account'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } const {storefront, session} = context; @@ -85,7 +85,7 @@ export async function action({request, context}: ActionFunctionArgs) { customerAccessTokenCreate?.customerAccessToken, ); - return json( + return data( {error: null, newCustomer}, { status: 302, @@ -96,9 +96,9 @@ export async function action({request, context}: ActionFunctionArgs) { ); } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/legacy-customer-account-flow/app/routes/account_.reset.$id.$resetToken.tsx b/examples/legacy-customer-account-flow/app/routes/account_.reset.$id.$resetToken.tsx index 9838590d1f..205e4ab9b8 100644 --- a/examples/legacy-customer-account-flow/app/routes/account_.reset.$id.$resetToken.tsx +++ b/examples/legacy-customer-account-flow/app/routes/account_.reset.$id.$resetToken.tsx @@ -1,4 +1,4 @@ -import {type ActionFunctionArgs, json, redirect} from '@shopify/remix-oxygen'; +import {type ActionFunctionArgs, data, redirect} from '@shopify/remix-oxygen'; import {Form, useActionData, type MetaFunction} from '@remix-run/react'; type ActionResponse = { @@ -11,7 +11,7 @@ export const meta: MetaFunction = () => { export async function action({request, context, params}: ActionFunctionArgs) { if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } const {id, resetToken} = params; const {session, storefront} = context; @@ -50,9 +50,9 @@ export async function action({request, context, params}: ActionFunctionArgs) { return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/legacy-customer-account-flow/app/routes/cart.tsx b/examples/legacy-customer-account-flow/app/routes/cart.tsx index afc7cc057f..c8f5e49e59 100644 --- a/examples/legacy-customer-account-flow/app/routes/cart.tsx +++ b/examples/legacy-customer-account-flow/app/routes/cart.tsx @@ -1,7 +1,7 @@ import {type MetaFunction, useLoaderData} from '@remix-run/react'; import type {CartQueryDataReturn} from '@shopify/hydrogen'; import {CartForm} from '@shopify/hydrogen'; -import {json, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; +import {data, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; import {CartMain} from '~/components/CartMain'; import type {RootLoader} from '~/root'; @@ -93,7 +93,7 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - return json( + return data( { cart: cartResult, errors, @@ -107,7 +107,7 @@ export async function action({request, context}: ActionFunctionArgs) { export async function loader({context}: LoaderFunctionArgs) { const {cart} = context; - return json(await cart.get()); + return await cart.get(); } export default function Cart() { diff --git a/examples/legacy-customer-account-flow/tsconfig.json b/examples/legacy-customer-account-flow/tsconfig.json index 110d781eea..3bf5db6c41 100644 --- a/examples/legacy-customer-account-flow/tsconfig.json +++ b/examples/legacy-customer-account-flow/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../templates/skeleton/tsconfig.json", - "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"], + "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "../../templates/skeleton/vite.config.ts"], "compilerOptions": { "baseUrl": ".", "paths": { diff --git a/examples/metaobjects/README.md b/examples/metaobjects/README.md index 584f9706f7..951b547326 100644 --- a/examples/metaobjects/README.md +++ b/examples/metaobjects/README.md @@ -76,11 +76,13 @@ To enable the Edit Route button return the env variable as `publicStoreSubdomain like so ```ts +import {data, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; + export async function loader({context}: LoaderFunctionArgs) { // other code ... const publicStoreDomain = context.env.PUBLIC_STORE_DOMAIN; - return defer( + return data( { // other code ... publicStoreSubdomain: context.env.PUBLIC_SHOPIFY_STORE_DOMAIN, @@ -109,7 +111,7 @@ export async function loader({context}: LoaderFunctionArgs) { cache: storefront.CacheNone(), }); - return json({route}); + return {route}; } ``` diff --git a/examples/metaobjects/app/root.tsx b/examples/metaobjects/app/root.tsx index 644cac9432..fd5bc8604e 100644 --- a/examples/metaobjects/app/root.tsx +++ b/examples/metaobjects/app/root.tsx @@ -1,5 +1,5 @@ import {useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -87,7 +87,7 @@ export async function loader({context}: LoaderFunctionArgs) { }, }); - return defer({ + return { cart: cartPromise, footer: footerPromise, header: await headerPromise, @@ -110,7 +110,7 @@ export async function loader({context}: LoaderFunctionArgs) { publictoreSubdomain: context.env.PUBLIC_SHOPIFY_STORE_DOMAIN, /********** EXAMPLE UPDATE END ************/ /***********************************************/ - }); + }; } export function Layout({children}: {children?: React.ReactNode}) { diff --git a/examples/metaobjects/app/routes/_index.tsx b/examples/metaobjects/app/routes/_index.tsx index 2c73a22cea..d08f5199f8 100644 --- a/examples/metaobjects/app/routes/_index.tsx +++ b/examples/metaobjects/app/routes/_index.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; /***********************************************/ @@ -20,7 +20,7 @@ export async function loader({context}: LoaderFunctionArgs) { cache: storefront.CacheNone(), }); - return json({route}); + return {route}; } export default function Homepage() { diff --git a/examples/metaobjects/app/routes/stores.$name.tsx b/examples/metaobjects/app/routes/stores.$name.tsx index 0aac0bc40d..8985926202 100644 --- a/examples/metaobjects/app/routes/stores.$name.tsx +++ b/examples/metaobjects/app/routes/stores.$name.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; // 1. Add metaobject content imports @@ -16,7 +16,7 @@ export async function loader({context, params}: LoaderFunctionArgs) { variables: {handle: `route-${name}`}, }); - return json({route}); + return {route}; } export default function Store() { diff --git a/examples/metaobjects/app/routes/stores._index.tsx b/examples/metaobjects/app/routes/stores._index.tsx index 5a4a032303..a2a63020d3 100644 --- a/examples/metaobjects/app/routes/stores._index.tsx +++ b/examples/metaobjects/app/routes/stores._index.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; // 1. Add metaobject content imports @@ -17,7 +17,7 @@ export async function loader({context}: LoaderFunctionArgs) { cache: storefront.CacheNone(), }); - return json({route}); + return {route}; } export default function Stores() { diff --git a/examples/metaobjects/tsconfig.json b/examples/metaobjects/tsconfig.json index 110d781eea..3bf5db6c41 100644 --- a/examples/metaobjects/tsconfig.json +++ b/examples/metaobjects/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../templates/skeleton/tsconfig.json", - "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"], + "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "../../templates/skeleton/vite.config.ts"], "compilerOptions": { "baseUrl": ".", "paths": { diff --git a/examples/multipass/app/root.tsx b/examples/multipass/app/root.tsx index 8b13edb28e..cc3b6ef801 100644 --- a/examples/multipass/app/root.tsx +++ b/examples/multipass/app/root.tsx @@ -1,5 +1,5 @@ import {useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {data, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -99,7 +99,7 @@ export async function loader({context}: LoaderFunctionArgs) { }, }); - return defer( + return data( { cart: cartPromise, footer: footerPromise, diff --git a/examples/multipass/app/routes/account.addresses.tsx b/examples/multipass/app/routes/account.addresses.tsx index 23ff85b0df..76ae8b40ae 100644 --- a/examples/multipass/app/routes/account.addresses.tsx +++ b/examples/multipass/app/routes/account.addresses.tsx @@ -1,7 +1,7 @@ import type {MailingAddressInput} from '@shopify/hydrogen/storefront-api-types'; import type {AddressFragment, CustomerFragment} from 'storefrontapi.generated'; import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -33,7 +33,7 @@ export async function loader({context}: LoaderFunctionArgs) { if (!customerAccessToken) { return redirect('/account/login'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { @@ -51,7 +51,7 @@ export async function action({request, context}: ActionFunctionArgs) { const customerAccessToken = await session.get('customerAccessToken'); if (!customerAccessToken) { - return json({error: {[addressId]: 'Unauthorized'}}, {status: 401}); + return data({error: {[addressId]: 'Unauthorized'}}, {status: 401}); } const {accessToken} = customerAccessToken; @@ -120,12 +120,12 @@ export async function action({request, context}: ActionFunctionArgs) { } } - return json({error: null, createdAddress, defaultAddress}); + return {error: null, createdAddress, defaultAddress}; } catch (error: unknown) { if (error instanceof Error) { - return json({error: {[addressId]: error.message}}, {status: 400}); + return data({error: {[addressId]: error.message}}, {status: 400}); } - return json({error: {[addressId]: error}}, {status: 400}); + return data({error: {[addressId]: error}}, {status: 400}); } } @@ -167,12 +167,12 @@ export async function action({request, context}: ActionFunctionArgs) { } } - return json({error: null, updatedAddress, defaultAddress}); + return {error: null, updatedAddress, defaultAddress}; } catch (error: unknown) { if (error instanceof Error) { - return json({error: {[addressId]: error.message}}, {status: 400}); + return data({error: {[addressId]: error.message}}, {status: 400}); } - return json({error: {[addressId]: error}}, {status: 400}); + return data({error: {[addressId]: error}}, {status: 400}); } } @@ -190,17 +190,17 @@ export async function action({request, context}: ActionFunctionArgs) { const error = customerAddressDelete.customerUserErrors[0]; throw new Error(error.message); } - return json({error: null, deletedAddress: addressId}); + return {error: null, deletedAddress: addressId}; } catch (error: unknown) { if (error instanceof Error) { - return json({error: {[addressId]: error.message}}, {status: 400}); + return data({error: {[addressId]: error.message}}, {status: 400}); } - return json({error: {[addressId]: error}}, {status: 400}); + return data({error: {[addressId]: error}}, {status: 400}); } } default: { - return json( + return data( {error: {[addressId]: 'Method not allowed'}}, {status: 405}, ); @@ -208,9 +208,9 @@ export async function action({request, context}: ActionFunctionArgs) { } } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/multipass/app/routes/account.orders.$id.tsx b/examples/multipass/app/routes/account.orders.$id.tsx index 9d4feb4a4f..932ee97af9 100644 --- a/examples/multipass/app/routes/account.orders.$id.tsx +++ b/examples/multipass/app/routes/account.orders.$id.tsx @@ -1,4 +1,4 @@ -import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import {Money, Image, flattenConnection} from '@shopify/hydrogen'; import type {OrderLineItemFullFragment} from 'storefrontapi.generated'; @@ -41,12 +41,12 @@ export async function loader({params, context}: LoaderFunctionArgs) { firstDiscount?.__typename === 'PricingPercentageValue' && firstDiscount?.percentage; - return json({ + return { order, lineItems, discountValue, discountPercentage, - }); + }; } export default function OrderRoute() { diff --git a/examples/multipass/app/routes/account.orders._index.tsx b/examples/multipass/app/routes/account.orders._index.tsx index 4604fb568d..d5bda5f2f3 100644 --- a/examples/multipass/app/routes/account.orders._index.tsx +++ b/examples/multipass/app/routes/account.orders._index.tsx @@ -1,6 +1,6 @@ import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import {Money, Pagination, getPaginationVariables} from '@shopify/hydrogen'; -import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {data, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import type { CustomerOrdersFragment, OrderItemFragment, @@ -37,12 +37,12 @@ export async function loader({request, context}: LoaderFunctionArgs) { throw new Error('Customer not found'); } - return json({customer}); + return {customer}; } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/multipass/app/routes/account.profile.tsx b/examples/multipass/app/routes/account.profile.tsx index 3edf686d8a..b1a834b302 100644 --- a/examples/multipass/app/routes/account.profile.tsx +++ b/examples/multipass/app/routes/account.profile.tsx @@ -1,7 +1,7 @@ import type {CustomerFragment} from 'storefrontapi.generated'; import type {CustomerUpdateInput} from '@shopify/hydrogen/storefront-api-types'; import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -28,20 +28,20 @@ export async function loader({context}: LoaderFunctionArgs) { if (!customerAccessToken) { return redirect('/account/login'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { const {session, storefront} = context; if (request.method !== 'PUT') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } const form = await request.formData(); const customerAccessToken = await session.get('customerAccessToken'); if (!customerAccessToken) { - return json({error: 'Unauthorized'}, {status: 401}); + return data({error: 'Unauthorized'}, {status: 401}); } try { @@ -80,7 +80,7 @@ export async function action({request, context}: ActionFunctionArgs) { // check for mutation errors if (updated.customerUpdate?.customerUserErrors?.length) { - return json( + return data( {error: updated.customerUpdate?.customerUserErrors[0]}, {status: 400}, ); @@ -94,9 +94,9 @@ export async function action({request, context}: ActionFunctionArgs) { ); } - return json({error: null, customer: updated.customerUpdate?.customer}); + return {error: null, customer: updated.customerUpdate?.customer}; } catch (error: any) { - return json({error: error.message, customer: null}, {status: 400}); + return data({error: error.message, customer: null}, {status: 400}); } } diff --git a/examples/multipass/app/routes/account.tsx b/examples/multipass/app/routes/account.tsx index 913cd65a93..6547c07f92 100644 --- a/examples/multipass/app/routes/account.tsx +++ b/examples/multipass/app/routes/account.tsx @@ -1,5 +1,5 @@ import {Form, NavLink, Outlet, useLoaderData} from '@remix-run/react'; -import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {data, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import type {CustomerFragment} from 'storefrontapi.generated'; export function shouldRevalidate() { @@ -23,12 +23,12 @@ export async function loader({request, context}: LoaderFunctionArgs) { return redirect('/account/login'); } else { // public subroute such as /account/login... - return json({ + return { isLoggedIn: false, isAccountHome, isPrivateRoute, customer: null, - }); + }; } } else { // loggedIn, default redirect to the orders page @@ -51,7 +51,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { throw new Error('Customer not found'); } - return json( + return data( {isLoggedIn, isPrivateRoute, isAccountHome, customer}, { headers: { diff --git a/examples/multipass/app/routes/account_.activate.$id.$activationToken.tsx b/examples/multipass/app/routes/account_.activate.$id.$activationToken.tsx index 49d1c4106e..cd7f55ba94 100644 --- a/examples/multipass/app/routes/account_.activate.$id.$activationToken.tsx +++ b/examples/multipass/app/routes/account_.activate.$id.$activationToken.tsx @@ -1,5 +1,5 @@ import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -18,7 +18,7 @@ export async function loader({context}: LoaderFunctionArgs) { if (await context.session.get('customerAccessToken')) { return redirect('/account'); } - return json({}); + return {}; } export async function action({request, context, params}: ActionFunctionArgs) { @@ -26,7 +26,7 @@ export async function action({request, context, params}: ActionFunctionArgs) { const {id, activationToken} = params; if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } try { @@ -73,9 +73,9 @@ export async function action({request, context, params}: ActionFunctionArgs) { return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/multipass/app/routes/account_.login.multipass.tsx b/examples/multipass/app/routes/account_.login.multipass.tsx index bd28c6712b..07a26074a3 100644 --- a/examples/multipass/app/routes/account_.login.multipass.tsx +++ b/examples/multipass/app/routes/account_.login.multipass.tsx @@ -1,5 +1,5 @@ import { - json, + data as remixData, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -124,7 +124,7 @@ export async function action({request, context}: ActionFunctionArgs) { } // success, return token, url - return json( + return remixData( {data: {...data, error: null}}, { status: 200, @@ -162,7 +162,7 @@ export async function action({request, context}: ActionFunctionArgs) { } function handleMethodNotAllowed() { - return json( + return remixData( { data: null, error: 'Method not allowed.', @@ -175,7 +175,7 @@ function handleMethodNotAllowed() { } function handleOptionsPreflight(origin: string) { - return json(null, { + return remixData(null, { status: 204, headers: getCorsHeaders(origin), }); @@ -207,7 +207,7 @@ async function handleLoggedOutResponse(options: { // For example, checkoutDomain `checkout.hydrogen.shop` or `shop.example.com` or `{shop}.myshopify.com`. const logOutUrl = `https://${checkoutDomain}/account/logout?return_url=${encodedCheckoutUrl}&step=contact_information`; - return json({data: {url: logOutUrl}, error: null}); + return {data: {url: logOutUrl}, error: null}; } /* @@ -238,7 +238,7 @@ function notLoggedInResponse(options: NotLoggedInResponseType) { } // Always return the original URL. - return json({data: {url}, error}); + return {data: {url}, error}; } function getCorsHeaders(origin: string): {[key: string]: string} { diff --git a/examples/multipass/app/routes/account_.login.tsx b/examples/multipass/app/routes/account_.login.tsx index f3537f8ae2..fc3564188c 100644 --- a/examples/multipass/app/routes/account_.login.tsx +++ b/examples/multipass/app/routes/account_.login.tsx @@ -1,5 +1,5 @@ import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -18,14 +18,14 @@ export async function loader({context}: LoaderFunctionArgs) { if (await context.session.get('customerAccessToken')) { return redirect('/account'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { const {session, storefront} = context; if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } try { @@ -57,9 +57,9 @@ export async function action({request, context}: ActionFunctionArgs) { return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/multipass/app/routes/account_.logout.tsx b/examples/multipass/app/routes/account_.logout.tsx index 8981fc3993..31a41bc501 100644 --- a/examples/multipass/app/routes/account_.logout.tsx +++ b/examples/multipass/app/routes/account_.logout.tsx @@ -1,4 +1,4 @@ -import {json, redirect, type ActionFunctionArgs} from '@shopify/remix-oxygen'; +import {data, redirect, type ActionFunctionArgs} from '@shopify/remix-oxygen'; import {type MetaFunction} from '@remix-run/react'; export const meta: MetaFunction = () => { @@ -14,7 +14,7 @@ export async function action({request, context}: ActionFunctionArgs) { session.unset('customerAccessToken'); if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } return redirect('/'); diff --git a/examples/multipass/app/routes/account_.recover.tsx b/examples/multipass/app/routes/account_.recover.tsx index 25a20fea91..aded9d8530 100644 --- a/examples/multipass/app/routes/account_.recover.tsx +++ b/examples/multipass/app/routes/account_.recover.tsx @@ -1,5 +1,5 @@ import { - json, + data, redirect, type LoaderFunctionArgs, type ActionFunctionArgs, @@ -17,7 +17,7 @@ export async function loader({context}: LoaderFunctionArgs) { return redirect('/account'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { @@ -26,7 +26,7 @@ export async function action({request, context}: ActionFunctionArgs) { const email = form.has('email') ? String(form.get('email')) : null; if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } try { @@ -37,13 +37,13 @@ export async function action({request, context}: ActionFunctionArgs) { variables: {email}, }); - return json({resetRequested: true}); + return {resetRequested: true}; } catch (error: unknown) { const resetRequested = false; if (error instanceof Error) { - return json({error: error.message, resetRequested}, {status: 400}); + return data({error: error.message, resetRequested}, {status: 400}); } - return json({error, resetRequested}, {status: 400}); + return data({error, resetRequested}, {status: 400}); } } diff --git a/examples/multipass/app/routes/account_.register.tsx b/examples/multipass/app/routes/account_.register.tsx index 2bfa62a39e..64c8a104ee 100644 --- a/examples/multipass/app/routes/account_.register.tsx +++ b/examples/multipass/app/routes/account_.register.tsx @@ -1,5 +1,5 @@ import { - json, + data, redirect, type ActionFunctionArgs, type LoaderFunctionArgs, @@ -20,12 +20,12 @@ export async function loader({context}: LoaderFunctionArgs) { return redirect('/account'); } - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } const {storefront, session} = context; @@ -85,7 +85,7 @@ export async function action({request, context}: ActionFunctionArgs) { customerAccessTokenCreate?.customerAccessToken, ); - return json( + return data( {error: null, newCustomer}, { status: 302, @@ -96,9 +96,9 @@ export async function action({request, context}: ActionFunctionArgs) { ); } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/multipass/app/routes/account_.reset.$id.$resetToken.tsx b/examples/multipass/app/routes/account_.reset.$id.$resetToken.tsx index 9838590d1f..205e4ab9b8 100644 --- a/examples/multipass/app/routes/account_.reset.$id.$resetToken.tsx +++ b/examples/multipass/app/routes/account_.reset.$id.$resetToken.tsx @@ -1,4 +1,4 @@ -import {type ActionFunctionArgs, json, redirect} from '@shopify/remix-oxygen'; +import {type ActionFunctionArgs, data, redirect} from '@shopify/remix-oxygen'; import {Form, useActionData, type MetaFunction} from '@remix-run/react'; type ActionResponse = { @@ -11,7 +11,7 @@ export const meta: MetaFunction = () => { export async function action({request, context, params}: ActionFunctionArgs) { if (request.method !== 'POST') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } const {id, resetToken} = params; const {session, storefront} = context; @@ -50,9 +50,9 @@ export async function action({request, context, params}: ActionFunctionArgs) { return redirect('/account'); } catch (error: unknown) { if (error instanceof Error) { - return json({error: error.message}, {status: 400}); + return data({error: error.message}, {status: 400}); } - return json({error}, {status: 400}); + return data({error}, {status: 400}); } } diff --git a/examples/multipass/app/routes/cart.tsx b/examples/multipass/app/routes/cart.tsx index ecbc71dcfc..6f10b7cc87 100644 --- a/examples/multipass/app/routes/cart.tsx +++ b/examples/multipass/app/routes/cart.tsx @@ -1,7 +1,7 @@ import {type MetaFunction, useLoaderData} from '@remix-run/react'; import type {CartQueryDataReturn} from '@shopify/hydrogen'; import {CartForm} from '@shopify/hydrogen'; -import {json, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; +import {data, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; import {CartMain} from '~/components/Cart'; export const meta: MetaFunction = () => { @@ -92,7 +92,7 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - return json( + return data( { cart: cartResult, errors, @@ -106,7 +106,7 @@ export async function action({request, context}: ActionFunctionArgs) { export async function loader({context}: LoaderFunctionArgs) { const {cart} = context; - return json(await cart.get()); + return await cart.get(); } export default function Cart() { diff --git a/examples/multipass/vite.config.ts b/examples/multipass/vite.config.ts index d0ce6d1328..d26be954c7 100644 --- a/examples/multipass/vite.config.ts +++ b/examples/multipass/vite.config.ts @@ -4,6 +4,12 @@ import {oxygen} from '@shopify/mini-oxygen/vite'; import {vitePlugin as remix} from '@remix-run/dev'; import tsconfigPaths from 'vite-tsconfig-paths'; +declare module "@remix-run/server-runtime" { + interface Future { + v3_singleFetch: true; + } +} + export default defineConfig({ plugins: [ hydrogen(), @@ -15,6 +21,7 @@ export default defineConfig({ v3_relativeSplatPath: true, v3_throwAbortReason: true, v3_lazyRouteDiscovery: true, + v3_singleFetch: true, }, }), tsconfigPaths(), diff --git a/examples/partytown/README.md b/examples/partytown/README.md index f2def26664..964981fcdd 100644 --- a/examples/partytown/README.md +++ b/examples/partytown/README.md @@ -98,7 +98,7 @@ Update the `loader` function ```ts export async function loader({context}: LoaderFunctionArgs) { const layout = await context.storefront.query<{shop: Shop}>(LAYOUT_QUERY); - return json( + return data( { layout, // 1. Pass the GTM container ID to the client diff --git a/examples/partytown/app/entry.server.tsx b/examples/partytown/app/entry.server.tsx index 7b1b4e72fb..ba1b0ae7dd 100644 --- a/examples/partytown/app/entry.server.tsx +++ b/examples/partytown/app/entry.server.tsx @@ -25,7 +25,7 @@ export default async function handleRequest( const body = await renderToReadableStream( - + , { nonce, diff --git a/examples/partytown/app/root.tsx b/examples/partytown/app/root.tsx index 19a5bdd0b8..455cda7b89 100644 --- a/examples/partytown/app/root.tsx +++ b/examples/partytown/app/root.tsx @@ -1,5 +1,5 @@ import {Script, useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -81,7 +81,7 @@ export async function loader(args: LoaderFunctionArgs) { const {storefront, env} = args.context; - return defer({ + return { ...deferredData, ...criticalData, publicStoreDomain: env.PUBLIC_STORE_DOMAIN, @@ -102,7 +102,7 @@ export async function loader(args: LoaderFunctionArgs) { gtmContainerId: args.context.env.GTM_CONTAINER_ID, /********** EXAMPLE UPDATE END ************/ /***********************************************/ - }); + }; } /** diff --git a/examples/partytown/tsconfig.json b/examples/partytown/tsconfig.json index 53b8b07351..d64ff30108 100644 --- a/examples/partytown/tsconfig.json +++ b/examples/partytown/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../templates/skeleton/tsconfig.json", - "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"], + "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "../../templates/skeleton/vite.config.ts"], "compilerOptions": { "baseUrl": ".", "paths": { diff --git a/examples/subscriptions/README.md b/examples/subscriptions/README.md index e385234f7d..df5ec3798a 100644 --- a/examples/subscriptions/README.md +++ b/examples/subscriptions/README.md @@ -229,7 +229,7 @@ export async function loader({params, request, context}: LoaderFunctionArgs) { const selectedVariant = product.variants.nodes[0]; // 5. Pass the selectedSellingPlan to the client - return json({product, selectedVariant, selectedSellingPlan}); + return {product, selectedVariant, selectedSellingPlan}; } ``` diff --git a/examples/subscriptions/app/routes/products.$handle.tsx b/examples/subscriptions/app/routes/products.$handle.tsx index 16ddaccdc3..31b6cf8539 100644 --- a/examples/subscriptions/app/routes/products.$handle.tsx +++ b/examples/subscriptions/app/routes/products.$handle.tsx @@ -1,5 +1,5 @@ import {Suspense} from 'react'; -import {defer, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Await, Link, @@ -129,7 +129,7 @@ export async function loader({params, request, context}: LoaderFunctionArgs) { variables: {handle}, }); - return defer({ + return { product, variants, /***********************************************/ @@ -138,7 +138,7 @@ export async function loader({params, request, context}: LoaderFunctionArgs) { selectedSellingPlan, /********** EXAMPLE UPDATE END ************/ /***********************************************/ - }); + }; } function redirectToFirstVariant({ diff --git a/examples/subscriptions/tsconfig.json b/examples/subscriptions/tsconfig.json index 5b672cc6e1..c54afd12a4 100644 --- a/examples/subscriptions/tsconfig.json +++ b/examples/subscriptions/tsconfig.json @@ -4,7 +4,8 @@ "./**/*.d.ts", "./**/*.ts", "./**/*.tsx", - "../../templates/skeleton/*.d.ts" + "../../templates/skeleton/*.d.ts", + "../../templates/skeleton/vite.config.ts" ], "compilerOptions": { "baseUrl": ".", diff --git a/examples/third-party-queries-caching/README.md b/examples/third-party-queries-caching/README.md index 74dde5f870..0a5298b39c 100644 --- a/examples/third-party-queries-caching/README.md +++ b/examples/third-party-queries-caching/README.md @@ -127,7 +127,7 @@ export async function loader({context}: LoaderFunctionArgs) { const {characters} = await context.rickAndMorty.query(CHARACTERS_QUERY, { cache: CacheShort(), }); - return json({characters}); + return {characters}; } ``` diff --git a/examples/third-party-queries-caching/app/routes/_index.tsx b/examples/third-party-queries-caching/app/routes/_index.tsx index 7739219a8b..4b9d6bcccd 100644 --- a/examples/third-party-queries-caching/app/routes/_index.tsx +++ b/examples/third-party-queries-caching/app/routes/_index.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData} from '@remix-run/react'; import {CacheShort} from '@shopify/hydrogen'; @@ -7,7 +7,7 @@ export async function loader({context}: LoaderFunctionArgs) { const {characters} = await context.rickAndMorty.query(CHARACTERS_QUERY, { cache: CacheShort(), }); - return json({characters}); + return {characters}; } type Character = { diff --git a/examples/third-party-queries-caching/tsconfig.json b/examples/third-party-queries-caching/tsconfig.json index 5b672cc6e1..c54afd12a4 100644 --- a/examples/third-party-queries-caching/tsconfig.json +++ b/examples/third-party-queries-caching/tsconfig.json @@ -4,7 +4,8 @@ "./**/*.d.ts", "./**/*.ts", "./**/*.tsx", - "../../templates/skeleton/*.d.ts" + "../../templates/skeleton/*.d.ts", + "../../templates/skeleton/vite.config.ts" ], "compilerOptions": { "baseUrl": ".", diff --git a/packages/cli/assets/vite/vite.config.js b/packages/cli/assets/vite/vite.config.js index 5ee9b626fb..f2da56a0a9 100644 --- a/packages/cli/assets/vite/vite.config.js +++ b/packages/cli/assets/vite/vite.config.js @@ -14,7 +14,6 @@ export default defineConfig({ v3_fetcherPersist: true, v3_relativeSplatPath: true, v3_throwAbortReason: true, - v3_lazyRouteDiscovery: true, }, }), tsconfigPaths(), diff --git a/packages/hydrogen/dev.env.d.ts b/packages/hydrogen/dev.env.d.ts index 8acc32f1d7..6bb59cf9e2 100644 --- a/packages/hydrogen/dev.env.d.ts +++ b/packages/hydrogen/dev.env.d.ts @@ -38,3 +38,9 @@ declare module '@shopify/remix-oxygen' { // declare local additions to the Remix session data here } } + +declare module '@remix-run/server-runtime' { + interface Future { + v3_singleFetch: true; + } +} diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.collectionView.example.jsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.collectionView.example.jsx index 256d67da51..13b838ce8c 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.collectionView.example.jsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.collectionView.example.jsx @@ -1,15 +1,14 @@ import {useLoaderData} from '@remix-run/react'; -import {json} from '@shopify/remix-oxygen'; import {Analytics} from '@shopify/hydrogen'; export async function loader() { - return json({ + return { collection: { id: '123', title: 'ABC', handle: 'abc', }, - }); + }; } export default function Collection() { diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.collectionView.example.tsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.collectionView.example.tsx index 5ede664723..ec45c2c72c 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.collectionView.example.tsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.collectionView.example.tsx @@ -1,15 +1,14 @@ import {useLoaderData} from '@remix-run/react'; -import {json} from '@shopify/remix-oxygen'; import {Analytics} from '@shopify/hydrogen'; export async function loader() { - return json({ + return { collection: { id: '123', title: 'ABC', handle: 'abc', }, - }); + }; } export default function Collection() { diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.example.jsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.example.jsx index fcafd50ae0..a5c9445726 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.example.jsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.example.jsx @@ -1,12 +1,11 @@ import {Analytics, getShopAnalytics} from '@shopify/hydrogen'; -import {defer} from '@shopify/remix-oxygen'; import {Outlet, useLoaderData} from '@remix-run/react'; export async function loader({context}) { const {cart, env} = context; const cartPromise = cart.get(); - return defer({ + return { cart: cartPromise, shop: getShopAnalytics(context), consent: { @@ -17,7 +16,7 @@ export async function loader({context}) { country: context.storefront.i18n.country, language: context.storefront.i18n.language, }, - }); + }; } export default function App() { diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.example.tsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.example.tsx index 2d7ec489cb..1786a5003a 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.example.tsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.example.tsx @@ -1,12 +1,12 @@ import {Analytics, getShopAnalytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Outlet, useLoaderData} from '@remix-run/react'; export async function loader({context}: LoaderFunctionArgs) { const {cart, env} = context; const cartPromise = cart.get(); - return defer({ + return { cart: cartPromise, shop: getShopAnalytics({ storefront: context.storefront, @@ -20,7 +20,7 @@ export async function loader({context}: LoaderFunctionArgs) { country: context.storefront.i18n.country, language: context.storefront.i18n.language, }, - }); + }; } export default function App() { diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.productView.example.jsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.productView.example.jsx index 6d59bf5689..5f3b698e8e 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.productView.example.jsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.productView.example.jsx @@ -1,9 +1,8 @@ import {useLoaderData} from '@remix-run/react'; -import {json} from '@shopify/remix-oxygen'; import {Analytics} from '@shopify/hydrogen'; export async function loader() { - return json({ + return { product: { id: '123', title: 'ABC', @@ -16,7 +15,7 @@ export async function loader() { }, }, }, - }); + }; } export default function Product() { diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.productView.example.tsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.productView.example.tsx index fa0184d81b..ca9b5c5938 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.productView.example.tsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.productView.example.tsx @@ -1,9 +1,8 @@ import {useLoaderData} from '@remix-run/react'; -import {json} from '@shopify/remix-oxygen'; import {Analytics} from '@shopify/hydrogen'; export async function loader() { - return json({ + return { product: { id: '123', title: 'ABC', @@ -16,7 +15,7 @@ export async function loader() { }, }, }, - }); + }; } export default function Product() { diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.searchView.example.jsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.searchView.example.jsx index b2b7c9a57f..4b772deab5 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.searchView.example.jsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.searchView.example.jsx @@ -1,5 +1,4 @@ import {Analytics} from '@shopify/hydrogen'; -import {json} from '@shopify/remix-oxygen'; import {useLoaderData} from '@remix-run/react'; export async function loader({request}) { @@ -7,9 +6,7 @@ export async function loader({request}) { const searchParams = new URLSearchParams(url.search); const searchTerm = String(searchParams.get('q') || ''); - return json({ - searchTerm, - }); + return {searchTerm}; } export default function SearchPage() { diff --git a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.searchView.example.tsx b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.searchView.example.tsx index 3209c867b1..dfa1defffe 100644 --- a/packages/hydrogen/src/analytics-manager/AnalyticsProvider.searchView.example.tsx +++ b/packages/hydrogen/src/analytics-manager/AnalyticsProvider.searchView.example.tsx @@ -1,5 +1,5 @@ import {Analytics} from '@shopify/hydrogen'; -import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData} from '@remix-run/react'; export async function loader({request}: LoaderFunctionArgs) { @@ -7,9 +7,7 @@ export async function loader({request}: LoaderFunctionArgs) { const searchParams = new URLSearchParams(url.search); const searchTerm = String(searchParams.get('q') || ''); - return json({ - searchTerm, - }); + return {searchTerm}; } export default function SearchPage() { diff --git a/packages/hydrogen/src/analytics-manager/getShopAnalytics.example.jsx b/packages/hydrogen/src/analytics-manager/getShopAnalytics.example.jsx index ac4a64ab8f..2a1bcf6be7 100644 --- a/packages/hydrogen/src/analytics-manager/getShopAnalytics.example.jsx +++ b/packages/hydrogen/src/analytics-manager/getShopAnalytics.example.jsx @@ -1,12 +1,11 @@ import {Analytics, getShopAnalytics} from '@shopify/hydrogen'; -import {defer} from '@shopify/remix-oxygen'; import {Outlet, useLoaderData} from '@remix-run/react'; export async function loader({context}) { const {cart, env} = context; const cartPromise = cart.get(); - return defer({ + return { cart: cartPromise, shop: getShopAnalytics({ storefront: context.storefront, @@ -20,7 +19,7 @@ export async function loader({context}) { country: context.storefront.i18n.country, language: context.storefront.i18n.language, }, - }); + }; } export default function App() { diff --git a/packages/hydrogen/src/analytics-manager/getShopAnalytics.example.tsx b/packages/hydrogen/src/analytics-manager/getShopAnalytics.example.tsx index 2d7ec489cb..1786a5003a 100644 --- a/packages/hydrogen/src/analytics-manager/getShopAnalytics.example.tsx +++ b/packages/hydrogen/src/analytics-manager/getShopAnalytics.example.tsx @@ -1,12 +1,12 @@ import {Analytics, getShopAnalytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Outlet, useLoaderData} from '@remix-run/react'; export async function loader({context}: LoaderFunctionArgs) { const {cart, env} = context; const cartPromise = cart.get(); - return defer({ + return { cart: cartPromise, shop: getShopAnalytics({ storefront: context.storefront, @@ -20,7 +20,7 @@ export async function loader({context}: LoaderFunctionArgs) { country: context.storefront.i18n.country, language: context.storefront.i18n.language, }, - }); + }; } export default function App() { diff --git a/packages/hydrogen/src/cache/CacheCustom.example.js b/packages/hydrogen/src/cache/CacheCustom.example.js index b7eb3e680a..83407e7fd4 100644 --- a/packages/hydrogen/src/cache/CacheCustom.example.js +++ b/packages/hydrogen/src/cache/CacheCustom.example.js @@ -1,4 +1,3 @@ -import {json} from '@shopify/remix-oxygen'; import {CacheCustom} from '@shopify/hydrogen'; export async function loader({context}) { @@ -18,5 +17,5 @@ export async function loader({context}) { }, ); - return json(data); + return data; } diff --git a/packages/hydrogen/src/cache/CacheCustom.example.ts b/packages/hydrogen/src/cache/CacheCustom.example.ts index 91d4347f99..863d427f4f 100644 --- a/packages/hydrogen/src/cache/CacheCustom.example.ts +++ b/packages/hydrogen/src/cache/CacheCustom.example.ts @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {CacheCustom} from '@shopify/hydrogen'; export async function loader({context}: LoaderFunctionArgs) { @@ -18,5 +18,5 @@ export async function loader({context}: LoaderFunctionArgs) { }, ); - return json(data); + return data; } diff --git a/packages/hydrogen/src/cache/CacheLong.example.js b/packages/hydrogen/src/cache/CacheLong.example.js index 4f8daa59b4..539b399e23 100644 --- a/packages/hydrogen/src/cache/CacheLong.example.js +++ b/packages/hydrogen/src/cache/CacheLong.example.js @@ -1,4 +1,3 @@ -import {json} from '@shopify/remix-oxygen'; import {CacheLong} from '@shopify/hydrogen'; export async function loader({context}) { @@ -15,5 +14,5 @@ export async function loader({context}) { }, ); - return json(data); + return data; } diff --git a/packages/hydrogen/src/cache/CacheLong.example.ts b/packages/hydrogen/src/cache/CacheLong.example.ts index e983a03901..c169b1ff04 100644 --- a/packages/hydrogen/src/cache/CacheLong.example.ts +++ b/packages/hydrogen/src/cache/CacheLong.example.ts @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {CacheLong} from '@shopify/hydrogen'; export async function loader({context}: LoaderFunctionArgs) { @@ -15,5 +15,5 @@ export async function loader({context}: LoaderFunctionArgs) { }, ); - return json(data); + return data; } diff --git a/packages/hydrogen/src/cache/CacheNone.example.js b/packages/hydrogen/src/cache/CacheNone.example.js index bc64db636c..5243717f2a 100644 --- a/packages/hydrogen/src/cache/CacheNone.example.js +++ b/packages/hydrogen/src/cache/CacheNone.example.js @@ -1,4 +1,3 @@ -import {json} from '@shopify/remix-oxygen'; import {CacheNone} from '@shopify/hydrogen'; export async function loader({context}) { @@ -15,5 +14,5 @@ export async function loader({context}) { }, ); - return json(data); + return data; } diff --git a/packages/hydrogen/src/cache/CacheNone.example.ts b/packages/hydrogen/src/cache/CacheNone.example.ts index 5d4e7e8a9c..63a48a4668 100644 --- a/packages/hydrogen/src/cache/CacheNone.example.ts +++ b/packages/hydrogen/src/cache/CacheNone.example.ts @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {CacheNone} from '@shopify/hydrogen'; export async function loader({context}: LoaderFunctionArgs) { @@ -15,5 +15,5 @@ export async function loader({context}: LoaderFunctionArgs) { }, ); - return json(data); + return data; } diff --git a/packages/hydrogen/src/cache/CacheShort.example.js b/packages/hydrogen/src/cache/CacheShort.example.js index 8ecc934945..6f33b1a77e 100644 --- a/packages/hydrogen/src/cache/CacheShort.example.js +++ b/packages/hydrogen/src/cache/CacheShort.example.js @@ -1,4 +1,3 @@ -import {json} from '@shopify/remix-oxygen'; import {CacheShort} from '@shopify/hydrogen'; export async function loader({context}) { @@ -15,5 +14,5 @@ export async function loader({context}) { }, ); - return json(data); + return data; } diff --git a/packages/hydrogen/src/cache/CacheShort.example.ts b/packages/hydrogen/src/cache/CacheShort.example.ts index fa612b74a9..42698b01d0 100644 --- a/packages/hydrogen/src/cache/CacheShort.example.ts +++ b/packages/hydrogen/src/cache/CacheShort.example.ts @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {CacheShort} from '@shopify/hydrogen'; export async function loader({context}: LoaderFunctionArgs) { @@ -15,5 +15,5 @@ export async function loader({context}: LoaderFunctionArgs) { }, ); - return json(data); + return data; } diff --git a/packages/hydrogen/src/cache/generateCacheControlHeader.example.js b/packages/hydrogen/src/cache/generateCacheControlHeader.example.js index 5d36f7d598..7e685da89a 100644 --- a/packages/hydrogen/src/cache/generateCacheControlHeader.example.js +++ b/packages/hydrogen/src/cache/generateCacheControlHeader.example.js @@ -1,8 +1,8 @@ -import {json} from '@shopify/remix-oxygen'; +import {data} from '@shopify/remix-oxygen'; import {generateCacheControlHeader, CacheShort} from '@shopify/hydrogen'; export async function loader() { - return json( + return data( {some: 'data'}, { headers: { diff --git a/packages/hydrogen/src/cache/generateCacheControlHeader.example.ts b/packages/hydrogen/src/cache/generateCacheControlHeader.example.ts index 5d36f7d598..7e685da89a 100644 --- a/packages/hydrogen/src/cache/generateCacheControlHeader.example.ts +++ b/packages/hydrogen/src/cache/generateCacheControlHeader.example.ts @@ -1,8 +1,8 @@ -import {json} from '@shopify/remix-oxygen'; +import {data} from '@shopify/remix-oxygen'; import {generateCacheControlHeader, CacheShort} from '@shopify/hydrogen'; export async function loader() { - return json( + return data( {some: 'data'}, { headers: { diff --git a/packages/hydrogen/src/cart/CartForm.custom.example.jsx b/packages/hydrogen/src/cart/CartForm.custom.example.jsx index b62df0f03e..ddb73849e3 100644 --- a/packages/hydrogen/src/cart/CartForm.custom.example.jsx +++ b/packages/hydrogen/src/cart/CartForm.custom.example.jsx @@ -1,4 +1,4 @@ -import {json} from '@remix-run/server-runtime'; +import {data} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; @@ -39,5 +39,5 @@ export async function action({request, context}) { const headers = cart.setCartId(result.cart.id); - return json(result, {status, headers}); + return data(result, {status, headers}); } diff --git a/packages/hydrogen/src/cart/CartForm.custom.example.tsx b/packages/hydrogen/src/cart/CartForm.custom.example.tsx index 97926e9522..8acb77b793 100644 --- a/packages/hydrogen/src/cart/CartForm.custom.example.tsx +++ b/packages/hydrogen/src/cart/CartForm.custom.example.tsx @@ -1,4 +1,4 @@ -import {type ActionFunctionArgs, json} from '@remix-run/server-runtime'; +import {type ActionFunctionArgs, data} from '@remix-run/server-runtime'; import { type CartQueryDataReturn, type HydrogenCart, @@ -47,5 +47,5 @@ export async function action({request, context}: ActionFunctionArgs) { const headers = cart.setCartId(result.cart.id); - return json(result, {status, headers}); + return data(result, {status, headers}); } diff --git a/packages/hydrogen/src/cart/CartForm.example.jsx b/packages/hydrogen/src/cart/CartForm.example.jsx index 656fc8e767..4e8a9ae2bb 100644 --- a/packages/hydrogen/src/cart/CartForm.example.jsx +++ b/packages/hydrogen/src/cart/CartForm.example.jsx @@ -1,4 +1,4 @@ -import {json} from '@remix-run/server-runtime'; +import {data} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; @@ -38,5 +38,5 @@ export async function action({request, context}) { const headers = cart.setCartId(result.cart.id); - return json(result, {status, headers}); + return data(result, {status, headers}); } diff --git a/packages/hydrogen/src/cart/CartForm.example.tsx b/packages/hydrogen/src/cart/CartForm.example.tsx index b1af1539ad..7cbf8ffd75 100644 --- a/packages/hydrogen/src/cart/CartForm.example.tsx +++ b/packages/hydrogen/src/cart/CartForm.example.tsx @@ -1,4 +1,4 @@ -import {type ActionFunctionArgs, json} from '@remix-run/server-runtime'; +import {type ActionFunctionArgs, data} from '@remix-run/server-runtime'; import { type CartQueryDataReturn, type HydrogenCart, @@ -45,5 +45,5 @@ export async function action({request, context}: ActionFunctionArgs) { const headers = cart.setCartId(result.cart.id); - return json(result, {status, headers}); + return data(result, {status, headers}); } diff --git a/packages/hydrogen/src/cart/CartForm.fetcher.example.jsx b/packages/hydrogen/src/cart/CartForm.fetcher.example.jsx index bbaea31230..5f02675cb9 100644 --- a/packages/hydrogen/src/cart/CartForm.fetcher.example.jsx +++ b/packages/hydrogen/src/cart/CartForm.fetcher.example.jsx @@ -1,5 +1,5 @@ import {useFetcher} from '@remix-run/react'; -import {json} from '@remix-run/server-runtime'; +import {data} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; @@ -56,5 +56,5 @@ export async function action({request, context}) { const headers = cart.setCartId(result.cart.id); - return json(result, {status, headers}); + return data(result, {status, headers}); } diff --git a/packages/hydrogen/src/cart/CartForm.fetcher.example.tsx b/packages/hydrogen/src/cart/CartForm.fetcher.example.tsx index d80ff25005..ebfa358ab7 100644 --- a/packages/hydrogen/src/cart/CartForm.fetcher.example.tsx +++ b/packages/hydrogen/src/cart/CartForm.fetcher.example.tsx @@ -1,5 +1,5 @@ import {useFetcher} from '@remix-run/react'; -import {type ActionFunctionArgs, json} from '@remix-run/server-runtime'; +import {type ActionFunctionArgs, data} from '@remix-run/server-runtime'; import { type CartQueryDataReturn, type HydrogenCart, @@ -67,5 +67,5 @@ export async function action({request, context}: ActionFunctionArgs) { const headers = cart.setCartId(result.cart.id); - return json(result, {status, headers}); + return data(result, {status, headers}); } diff --git a/packages/hydrogen/src/cart/CartForm.input-tag.example.jsx b/packages/hydrogen/src/cart/CartForm.input-tag.example.jsx index a5ae40ce0e..b109a651bc 100644 --- a/packages/hydrogen/src/cart/CartForm.input-tag.example.jsx +++ b/packages/hydrogen/src/cart/CartForm.input-tag.example.jsx @@ -1,4 +1,4 @@ -import {json} from '@remix-run/server-runtime'; +import {data} from '@remix-run/server-runtime'; import {CartForm} from '@shopify/hydrogen'; import invariant from 'tiny-invariant'; @@ -28,5 +28,5 @@ export async function action({request, context}) { const headers = cart.setCartId(result.cart.id); - return json(result, {status, headers}); + return data(result, {status, headers}); } diff --git a/packages/hydrogen/src/cart/CartForm.input-tag.example.tsx b/packages/hydrogen/src/cart/CartForm.input-tag.example.tsx index 54d4586eb3..e04c4b6d66 100644 --- a/packages/hydrogen/src/cart/CartForm.input-tag.example.tsx +++ b/packages/hydrogen/src/cart/CartForm.input-tag.example.tsx @@ -1,4 +1,4 @@ -import {type ActionFunctionArgs, json} from '@remix-run/server-runtime'; +import {type ActionFunctionArgs, data} from '@remix-run/server-runtime'; import { type CartQueryDataReturn, type HydrogenCart, @@ -35,5 +35,5 @@ export async function action({request, context}: ActionFunctionArgs) { const headers = cart.setCartId(result.cart.id); - return json(result, {status, headers}); + return data(result, {status, headers}); } diff --git a/packages/hydrogen/src/cart/cartSetIdDefault.example.js b/packages/hydrogen/src/cart/cartSetIdDefault.example.js index cb65ce6682..1b9e94b653 100644 --- a/packages/hydrogen/src/cart/cartSetIdDefault.example.js +++ b/packages/hydrogen/src/cart/cartSetIdDefault.example.js @@ -1,4 +1,4 @@ -import {json} from '@remix-run/server-runtime'; +import {data} from '@remix-run/server-runtime'; import {cartGetIdDefault, cartSetIdDefault} from '@shopify/hydrogen'; // server.js @@ -22,5 +22,5 @@ export async function action({context}) { const headers = cart.setCartId(result.cart.id); - return json(result, {headers}); + return data(result, {headers}); } diff --git a/packages/hydrogen/src/cart/optimistic/useOptimisticCart.example.jsx b/packages/hydrogen/src/cart/optimistic/useOptimisticCart.example.jsx index e88dff13ea..652fffceda 100644 --- a/packages/hydrogen/src/cart/optimistic/useOptimisticCart.example.jsx +++ b/packages/hydrogen/src/cart/optimistic/useOptimisticCart.example.jsx @@ -1,12 +1,11 @@ -import {defer} from '@shopify/remix-oxygen'; import {Link} from '@remix-run/react'; import {CartForm, useOptimisticCart} from '@shopify/hydrogen'; // Root loader returns the cart data export async function loader({context}) { - return defer({ + return { cart: context.cart.get(), - }); + }; } // The cart component renders each line item in the cart. diff --git a/packages/hydrogen/src/cart/optimistic/useOptimisticCart.example.tsx b/packages/hydrogen/src/cart/optimistic/useOptimisticCart.example.tsx index 6c4de7bfdc..b5d99208d7 100644 --- a/packages/hydrogen/src/cart/optimistic/useOptimisticCart.example.tsx +++ b/packages/hydrogen/src/cart/optimistic/useOptimisticCart.example.tsx @@ -1,13 +1,13 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Link} from '@remix-run/react'; import {CartForm, useOptimisticCart} from '@shopify/hydrogen'; import type {Cart} from '@shopify/hydrogen/storefront-api-types'; // Root loader returns the cart data export async function loader({context}: LoaderFunctionArgs) { - return defer({ + return { cart: context.cart.get(), - }); + }; } // The cart component renders each line item in the cart. diff --git a/packages/hydrogen/src/csp/createContentSecurityPolicy.example.jsx b/packages/hydrogen/src/csp/createContentSecurityPolicy.example.jsx index e795a812b8..39283038b3 100644 --- a/packages/hydrogen/src/csp/createContentSecurityPolicy.example.jsx +++ b/packages/hydrogen/src/csp/createContentSecurityPolicy.example.jsx @@ -19,7 +19,7 @@ export default async function handleRequest( }); const body = await renderToReadableStream( - + , { nonce, diff --git a/packages/hydrogen/src/csp/createContentSecurityPolicy.example.tsx b/packages/hydrogen/src/csp/createContentSecurityPolicy.example.tsx index 127321376d..79195d917c 100644 --- a/packages/hydrogen/src/csp/createContentSecurityPolicy.example.tsx +++ b/packages/hydrogen/src/csp/createContentSecurityPolicy.example.tsx @@ -20,7 +20,7 @@ export default async function handleRequest( }); const body = await renderToReadableStream( - + , { nonce, diff --git a/packages/hydrogen/src/customer/customer.auth-handler.example.jsx b/packages/hydrogen/src/customer/customer.auth-handler.example.jsx index 4dbd7464ea..6128f4d7fe 100644 --- a/packages/hydrogen/src/customer/customer.auth-handler.example.jsx +++ b/packages/hydrogen/src/customer/customer.auth-handler.example.jsx @@ -101,7 +101,6 @@ import { isRouteErrorResponse, useLocation, } from '@remix-run/react'; -import {json} from '@shopify/remix-oxygen'; export async function loader({context}) { const {data} = await context.customerAccount.query(`#graphql @@ -113,7 +112,7 @@ export async function loader({context}) { } `); - return json({customer: data.customer}); + return {customer: data.customer}; } export function ErrorBoundary() { diff --git a/packages/hydrogen/src/customer/customer.auth-handler.example.tsx b/packages/hydrogen/src/customer/customer.auth-handler.example.tsx index a7e01db439..6a0deba273 100644 --- a/packages/hydrogen/src/customer/customer.auth-handler.example.tsx +++ b/packages/hydrogen/src/customer/customer.auth-handler.example.tsx @@ -132,7 +132,7 @@ import { isRouteErrorResponse, useLocation, } from '@remix-run/react'; -import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; export async function loader({context}: LoaderFunctionArgs) { const {data} = await context.customerAccount.query<{ @@ -146,7 +146,7 @@ export async function loader({context}: LoaderFunctionArgs) { } `); - return json({customer: data.customer}); + return {customer: data.customer}; } export function ErrorBoundary() { diff --git a/packages/hydrogen/src/customer/customer.opt-out-handler.example.jsx b/packages/hydrogen/src/customer/customer.opt-out-handler.example.jsx index e9e4d29ba7..6ac1a1e329 100644 --- a/packages/hydrogen/src/customer/customer.opt-out-handler.example.jsx +++ b/packages/hydrogen/src/customer/customer.opt-out-handler.example.jsx @@ -101,7 +101,6 @@ import { isRouteErrorResponse, useLocation, } from '@remix-run/react'; -import {json} from '@shopify/remix-oxygen'; export async function loader({context}) { if (!(await context.customerAccount.isLoggedIn())) { @@ -121,7 +120,7 @@ export async function loader({context}) { `, ); - return json({customer: data.customer}); + return {customer: data.customer}; } export function ErrorBoundary() { diff --git a/packages/hydrogen/src/customer/customer.opt-out-handler.example.tsx b/packages/hydrogen/src/customer/customer.opt-out-handler.example.tsx index 2960c3ff61..e33e9ea5b4 100644 --- a/packages/hydrogen/src/customer/customer.opt-out-handler.example.tsx +++ b/packages/hydrogen/src/customer/customer.opt-out-handler.example.tsx @@ -115,7 +115,7 @@ import { isRouteErrorResponse, useLocation, } from '@remix-run/react'; -import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; export async function loader({context}: LoaderFunctionArgs) { if (!(await context.customerAccount.isLoggedIn())) { @@ -135,7 +135,7 @@ export async function loader({context}: LoaderFunctionArgs) { `, ); - return json({customer: data.customer}); + return {customer: data.customer}; } export function ErrorBoundary() { diff --git a/packages/hydrogen/src/customer/customer.test.ts b/packages/hydrogen/src/customer/customer.test.ts index f3f7693df6..93d8a05159 100644 --- a/packages/hydrogen/src/customer/customer.test.ts +++ b/packages/hydrogen/src/customer/customer.test.ts @@ -1392,6 +1392,66 @@ describe('customer', () => { expect(customAuthStatusHandler).toHaveBeenCalledOnce(); } }); + + it('handles Remix `https://localhost/account/orders.data` url extensions when passing current path as param if logged out', async () => { + const customer = createCustomerAccountClient({ + session, + customerAccountId: 'customerAccountId', + customerAccountUrl: 'https://customer-api', + request: new Request('https://localhost/account/orders.data'), + waitUntil: vi.fn(), + }); + (session.get as any).mockReturnValueOnce(undefined); + + try { + await customer.handleAuthStatus(); + } catch (error) { + expect((error as Response).status).toBe(302); + expect((error as Response).headers.get('location')).toBe( + '/account/login?return_to=%2Faccount%2Forders', + ); + } + }); + + it('handles Remix `https://localhost/account/_root.data` url extensions when passing current path as param if logged out', async () => { + const customer = createCustomerAccountClient({ + session, + customerAccountId: 'customerAccountId', + customerAccountUrl: 'https://customer-api', + request: new Request('https://localhost/account/_root.data'), + waitUntil: vi.fn(), + }); + (session.get as any).mockReturnValueOnce(undefined); + + try { + await customer.handleAuthStatus(); + } catch (error) { + expect((error as Response).status).toBe(302); + expect((error as Response).headers.get('location')).toBe( + '/account/login?return_to=%2Faccount', + ); + } + }); + + it('handles Remix `https://localhost/_root.data` url extensions when passing current path as param if logged out', async () => { + const customer = createCustomerAccountClient({ + session, + customerAccountId: 'customerAccountId', + customerAccountUrl: 'https://customer-api', + request: new Request('https://localhost/_root.data'), + waitUntil: vi.fn(), + }); + (session.get as any).mockReturnValueOnce(undefined); + + try { + await customer.handleAuthStatus(); + } catch (error) { + expect((error as Response).status).toBe(302); + expect((error as Response).headers.get('location')).toBe( + '/account/login?return_to=%2F', + ); + } + }); }); describe('query', () => { diff --git a/packages/hydrogen/src/customer/customer.ts b/packages/hydrogen/src/customer/customer.ts index 306e738d17..20299f7ba5 100644 --- a/packages/hydrogen/src/customer/customer.ts +++ b/packages/hydrogen/src/customer/customer.ts @@ -59,9 +59,24 @@ function defaultAuthStatusHandler( const {pathname} = new URL(request.url); + /** + * Remix (single-fetch) request objects have different url + * paths when soft navigating. Examples: + * + * /_root.data - home page + * /collections.data - collections page + * + * These url annotations needs to be cleaned up before constructing urls to be passed as + * GET parameters for customer login url + */ + const cleanedPathname = pathname + .replace(/\.data$/, '') + .replace(/\/_root$/, '/') + .replace(/(.+)\/$/, '$1'); + const redirectTo = defaultLoginUrl + - `?${new URLSearchParams({return_to: pathname}).toString()}`; + `?${new URLSearchParams({return_to: cleanedPathname}).toString()}`; return redirect(redirectTo); } diff --git a/packages/hydrogen/src/pagination/Pagination.example.jsx b/packages/hydrogen/src/pagination/Pagination.example.jsx index 4219d429b9..e290db2ce2 100644 --- a/packages/hydrogen/src/pagination/Pagination.example.jsx +++ b/packages/hydrogen/src/pagination/Pagination.example.jsx @@ -1,4 +1,3 @@ -import {json} from '@shopify/remix-oxygen'; import {Pagination, getPaginationVariables} from '@shopify/hydrogen'; import {useLoaderData, Link} from '@remix-run/react'; @@ -9,7 +8,7 @@ export async function loader({request, context: {storefront}}) { variables, }); - return json({products: data.products}); + return {products: data.products}; } export default function List() { diff --git a/packages/hydrogen/src/pagination/Pagination.example.tsx b/packages/hydrogen/src/pagination/Pagination.example.tsx index a0c6743e7b..92159661cc 100644 --- a/packages/hydrogen/src/pagination/Pagination.example.tsx +++ b/packages/hydrogen/src/pagination/Pagination.example.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Pagination, getPaginationVariables} from '@shopify/hydrogen'; import {useLoaderData, Link} from '@remix-run/react'; import {ProductConnection} from '@shopify/hydrogen/storefront-api-types'; @@ -16,7 +16,7 @@ export async function loader({ }, ); - return json({products: data.products}); + return {products: data.products}; } export default function List() { diff --git a/packages/hydrogen/src/pagination/Pagination.multiple.example.jsx b/packages/hydrogen/src/pagination/Pagination.multiple.example.jsx index 11e0357633..e8d09a1d55 100644 --- a/packages/hydrogen/src/pagination/Pagination.multiple.example.jsx +++ b/packages/hydrogen/src/pagination/Pagination.multiple.example.jsx @@ -1,4 +1,3 @@ -import {json} from '@shopify/remix-oxygen'; import {useLoaderData, Link} from '@remix-run/react'; import {getPaginationVariables, Pagination} from '@shopify/hydrogen'; @@ -21,7 +20,7 @@ export async function loader({request, context: {storefront}}) { }), ]); - return json({womensProducts, mensProducts}); + return {womensProducts, mensProducts}; } export default function Collection() { diff --git a/packages/hydrogen/src/pagination/Pagination.multiple.example.tsx b/packages/hydrogen/src/pagination/Pagination.multiple.example.tsx index e2261f8d2f..702119311d 100644 --- a/packages/hydrogen/src/pagination/Pagination.multiple.example.tsx +++ b/packages/hydrogen/src/pagination/Pagination.multiple.example.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, Link} from '@remix-run/react'; import {getPaginationVariables, Pagination} from '@shopify/hydrogen'; import {type Collection} from '@shopify/hydrogen-react/storefront-api-types'; @@ -25,7 +25,7 @@ export async function loader({ }), ]); - return json({womensProducts, mensProducts}); + return {womensProducts, mensProducts}; } export default function Collection() { diff --git a/packages/hydrogen/src/product/getSelectedProductOptions.example.jsx b/packages/hydrogen/src/product/getSelectedProductOptions.example.jsx index 7b6ca69109..2534dc4972 100644 --- a/packages/hydrogen/src/product/getSelectedProductOptions.example.jsx +++ b/packages/hydrogen/src/product/getSelectedProductOptions.example.jsx @@ -1,5 +1,4 @@ import {getSelectedProductOptions} from '@shopify/hydrogen'; -import {json} from '@shopify/remix-oxygen'; export async function loader({request, params, context}) { const selectedOptions = getSelectedProductOptions(request); @@ -11,7 +10,7 @@ export async function loader({request, params, context}) { }, }); - return json({product}); + return {product}; } const PRODUCT_QUERY = `#graphql @@ -21,7 +20,7 @@ const PRODUCT_QUERY = `#graphql description options { name - values + values } selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions, ignoreUnknownOptions: true, caseInsensitiveMatch: true) { ...ProductVariantFragment diff --git a/packages/hydrogen/src/product/getSelectedProductOptions.example.tsx b/packages/hydrogen/src/product/getSelectedProductOptions.example.tsx index 45c1a453d2..41dff2b476 100644 --- a/packages/hydrogen/src/product/getSelectedProductOptions.example.tsx +++ b/packages/hydrogen/src/product/getSelectedProductOptions.example.tsx @@ -1,5 +1,5 @@ import {getSelectedProductOptions} from '@shopify/hydrogen'; -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; export async function loader({request, params, context}: LoaderFunctionArgs) { const selectedOptions = getSelectedProductOptions(request); @@ -11,7 +11,7 @@ export async function loader({request, params, context}: LoaderFunctionArgs) { }, }); - return json({product}); + return {product}; } const PRODUCT_QUERY = `#graphql @@ -21,7 +21,7 @@ const PRODUCT_QUERY = `#graphql description options { name - values + values } selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions, ignoreUnknownOptions: true, caseInsensitiveMatch: true) { ...ProductVariantFragment diff --git a/packages/hydrogen/src/product/useOptimisticVariant.example.jsx b/packages/hydrogen/src/product/useOptimisticVariant.example.jsx index 69cbbb0eab..5f7eef7e3a 100644 --- a/packages/hydrogen/src/product/useOptimisticVariant.example.jsx +++ b/packages/hydrogen/src/product/useOptimisticVariant.example.jsx @@ -1,13 +1,12 @@ import {useLoaderData} from '@remix-run/react'; -import {defer} from '@remix-run/server-runtime'; import {useOptimisticVariant} from '@shopify/hydrogen'; export async function loader({context}) { - return defer({ + return { product: await context.storefront.query('/** product query **/'), // Note that variants does not need to be awaited to be used by `useOptimisticVariant` variants: context.storefront.query('/** variants query **/'), - }); + }; } function Product() { diff --git a/packages/hydrogen/src/product/useOptimisticVariant.example.tsx b/packages/hydrogen/src/product/useOptimisticVariant.example.tsx index dd369418af..cdac06ccc5 100644 --- a/packages/hydrogen/src/product/useOptimisticVariant.example.tsx +++ b/packages/hydrogen/src/product/useOptimisticVariant.example.tsx @@ -1,13 +1,13 @@ import {useLoaderData} from '@remix-run/react'; -import {defer, LoaderFunctionArgs} from '@remix-run/server-runtime'; +import {LoaderFunctionArgs} from '@remix-run/server-runtime'; import {useOptimisticVariant} from '@shopify/hydrogen'; export async function loader({context}: LoaderFunctionArgs) { - return defer({ + return { product: await context.storefront.query('/** product query */'), // Note that variants does not need to be awaited to be used by `useOptimisticVariant` variants: context.storefront.query('/** variants query */'), - }); + }; } function Product() { diff --git a/packages/hydrogen/src/utils/graphql.ts b/packages/hydrogen/src/utils/graphql.ts index f0d9277df0..9b31c516d6 100644 --- a/packages/hydrogen/src/utils/graphql.ts +++ b/packages/hydrogen/src/utils/graphql.ts @@ -144,7 +144,7 @@ export class GraphQLError extends Error { /** * Note: toJSON` is internally used by `JSON.stringify(...)`. * The most common scenario when this error instance is going to be stringified is - * when it's passed to Remix' `json` and `defer` functions: e.g. `defer({promise: storefront.query(...)})`. + * when it's passed to Remix' `json` and `defer` functions: e.g. `{promise: storefront.query(...)}`. * In this situation, we don't want to expose private error information to the browser so we only * do it in development. */ diff --git a/packages/remix-oxygen/src/index.ts b/packages/remix-oxygen/src/index.ts index 80fb8c7160..33dce8b804 100644 --- a/packages/remix-oxygen/src/index.ts +++ b/packages/remix-oxygen/src/index.ts @@ -16,6 +16,7 @@ export { MaxPartSizeExceededError, redirect, redirectDocument, + data, } from '@remix-run/server-runtime'; export type { diff --git a/rfc/cart.md b/rfc/cart.md index 7b6f719827..5b92f367a7 100644 --- a/rfc/cart.md +++ b/rfc/cart.md @@ -70,7 +70,7 @@ export async function action({request, context}) { session.set('cartId', cartId); const {cart, errors} = result; - return json({cart, errors}, {status, headers}); + return data({cart, errors}, {status, headers}); } const USER_ERROR_FRAGMENT = `#graphql diff --git a/rfc/pagination.md b/rfc/pagination.md index c1d73df490..3e372b3200 100644 --- a/rfc/pagination.md +++ b/rfc/pagination.md @@ -39,7 +39,7 @@ export async function loader({context, request}: LoaderArgs) { throw new Response(null, {status: 404}); } - return json({products}); + return {products}; } ``` @@ -97,7 +97,7 @@ export async function loader({context, request}: LoaderArgs) { throw new Response(null, {status: 404}); } - return json({products}); + return {products}; } ``` diff --git a/templates/TEMPLATE_GUIDELINES.md b/templates/TEMPLATE_GUIDELINES.md index 42492fd8cb..c5bfdf7b2c 100644 --- a/templates/TEMPLATE_GUIDELINES.md +++ b/templates/TEMPLATE_GUIDELINES.md @@ -21,7 +21,6 @@ Always demonstrate realistic error-handling. Skeleton templates should be a shin - **Have an `ErrorBoundary` in every route template.** `ErrorBoundary` is used when an Error is thrown in a “loader”, and is generally meant for unexpected errors, like 500, 503, etc. Any Storefront query or mutation error will be handled by the `ErrorBoundary`. Type the error as “unknown” since _anything_ in JS can be thrown 🙂 - **Use the “errorElement” prop on every `` component.** When using “defer”, some promises may be rejected at a later time. The only way to handle this is to use the “errorElement” on the associated component, otherwise the error is swallowed. - **Use try/catch** – except in “loader”, “action”, and the Component. Those three “Route Module APIs” are handled automatically by `ErrorBoundary` and CatchBoundary, but the rest – such as “meta”, “links”, “handle”, etc. – will crash the server if an error is thrown. -- **Have a CatchBoundary if necessary.** A CatchBoundary is used when a new Response is thrown, and is generally meant for expected errors caused by the user, such as 401, 404, etc. Note that `CatchBoundary`s will be deprecated in Remix V2, at which time we'll remove this recommendation. ### Don’t @@ -45,7 +44,7 @@ export async function loader() { } //... - return defer() + return data } export function meta() { @@ -60,12 +59,6 @@ export function ErrorBoundary({error}) { return (
{error.message}
) } -// Note that `CatchBoundary`s will be deprecated in Remix V2 -export function CatchBoundary() { - const {statusText} = useCatch() - return (
{statusText}
) -} - export default function TheUIComponents() { return ( An error occurred}> @@ -124,7 +117,7 @@ Remix-specific route API functions should be ordered and consistent in style, to 1. Http header tweaks (`shouldRevalidate`, `headers`, `meta`, `links`) 1. Data manipulation (`loader`, `action`) 1. UI (`Component`) - 1. Error handling (`ErrorBoundary`, `CatchBoundary`) + 1. Error handling (`ErrorBoundary`) 1. Storefront API GraphQL query strings - Use function declarations when possible - Use the most specific type available for Remix Route APIs. @@ -158,8 +151,6 @@ export default function Component() {} export function ErrorBoundary() {} -export function CatchBoundary() {} - /* storefront Queries/Mutations, see more specific recommendations below */ ``` @@ -187,17 +178,18 @@ Use the correct return type in `loader()`, `action()`, etc. ### Do -- Use `json()` by default +- Return raw json object by default +- Use `await` if you want the data to be streamed in later - Use `redirect()` from the `@shopify/remix-oxygen` package to redirect -- Use `defer()` when there is a need to have content streamed in later -- Use `new Response()` for errors (like 404s) and for unique document responses like `.xml` and `.txt` +- Use `data()` for errors (like 404s) +- Use `new Response()` for unique document responses like `.xml` and `.txt` - Use capitalized and kebab-cased headers in responses, like `Cache-Control` ### Example ```tsx export async function loader() { - return json({foo: 'bar'}); + return {foo: 'bar'}; } ``` @@ -210,7 +202,7 @@ export async function loader() { ```tsx export async function loader() { - return json( + return data( {foo: 'bar'}, { headers: { diff --git a/templates/skeleton/app/entry.server.tsx b/templates/skeleton/app/entry.server.tsx index a0d062d76e..cce5daefd5 100644 --- a/templates/skeleton/app/entry.server.tsx +++ b/templates/skeleton/app/entry.server.tsx @@ -20,7 +20,7 @@ export default async function handleRequest( const body = await renderToReadableStream( - + , { nonce, diff --git a/templates/skeleton/app/root.tsx b/templates/skeleton/app/root.tsx index eeb97d99a5..17b8c59c1c 100644 --- a/templates/skeleton/app/root.tsx +++ b/templates/skeleton/app/root.tsx @@ -1,5 +1,5 @@ import {useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import { Links, Meta, @@ -70,7 +70,7 @@ export async function loader(args: LoaderFunctionArgs) { const {storefront, env} = args.context; - return defer({ + return { ...deferredData, ...criticalData, publicStoreDomain: env.PUBLIC_STORE_DOMAIN, @@ -86,7 +86,7 @@ export async function loader(args: LoaderFunctionArgs) { country: args.context.storefront.i18n.country, language: args.context.storefront.i18n.language, }, - }); + }; } /** diff --git a/templates/skeleton/app/routes/[robots.txt].tsx b/templates/skeleton/app/routes/[robots.txt].tsx index 3feeb475c9..d6109ab89f 100644 --- a/templates/skeleton/app/routes/[robots.txt].tsx +++ b/templates/skeleton/app/routes/[robots.txt].tsx @@ -1,5 +1,4 @@ import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; -import {useRouteError, isRouteErrorResponse} from '@remix-run/react'; import {parseGid} from '@shopify/hydrogen'; export async function loader({request, context}: LoaderFunctionArgs) { diff --git a/templates/skeleton/app/routes/_index.tsx b/templates/skeleton/app/routes/_index.tsx index 4b74fb03fd..9fa3364233 100644 --- a/templates/skeleton/app/routes/_index.tsx +++ b/templates/skeleton/app/routes/_index.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Await, useLoaderData, Link, type MetaFunction} from '@remix-run/react'; import {Suspense} from 'react'; import {Image, Money} from '@shopify/hydrogen'; @@ -18,7 +18,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/account.addresses.tsx b/templates/skeleton/app/routes/account.addresses.tsx index 448b9bf340..14cd1f3ff5 100644 --- a/templates/skeleton/app/routes/account.addresses.tsx +++ b/templates/skeleton/app/routes/account.addresses.tsx @@ -4,7 +4,7 @@ import type { CustomerFragment, } from 'customer-accountapi.generated'; import { - json, + data, type ActionFunctionArgs, type LoaderFunctionArgs, } from '@shopify/remix-oxygen'; @@ -38,7 +38,7 @@ export const meta: MetaFunction = () => { export async function loader({context}: LoaderFunctionArgs) { await context.customerAccount.handleAuthStatus(); - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { @@ -57,7 +57,7 @@ export async function action({request, context}: ActionFunctionArgs) { // this will ensure redirecting to login never happen for mutatation const isLoggedIn = await customerAccount.isLoggedIn(); if (!isLoggedIn) { - return json( + return data( {error: {[addressId]: 'Unauthorized'}}, { status: 401, @@ -112,21 +112,21 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer address create failed.'); } - return json({ + return { error: null, createdAddress: data?.customerAddressCreate?.customerAddress, defaultAddress, - }); + }; } catch (error: unknown) { if (error instanceof Error) { - return json( + return data( {error: {[addressId]: error.message}}, { status: 400, }, ); } - return json( + return data( {error: {[addressId]: error}}, { status: 400, @@ -161,21 +161,21 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer address update failed.'); } - return json({ + return { error: null, updatedAddress: address, defaultAddress, - }); + }; } catch (error: unknown) { if (error instanceof Error) { - return json( + return data( {error: {[addressId]: error.message}}, { status: 400, }, ); } - return json( + return data( {error: {[addressId]: error}}, { status: 400, @@ -206,17 +206,17 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer address delete failed.'); } - return json({error: null, deletedAddress: addressId}); + return {error: null, deletedAddress: addressId}; } catch (error: unknown) { if (error instanceof Error) { - return json( + return data( {error: {[addressId]: error.message}}, { status: 400, }, ); } - return json( + return data( {error: {[addressId]: error}}, { status: 400, @@ -226,7 +226,7 @@ export async function action({request, context}: ActionFunctionArgs) { } default: { - return json( + return data( {error: {[addressId]: 'Method not allowed'}}, { status: 405, @@ -236,14 +236,14 @@ export async function action({request, context}: ActionFunctionArgs) { } } catch (error: unknown) { if (error instanceof Error) { - return json( + return data( {error: error.message}, { status: 400, }, ); } - return json( + return data( {error}, { status: 400, diff --git a/templates/skeleton/app/routes/account.orders.$id.tsx b/templates/skeleton/app/routes/account.orders.$id.tsx index 6362e5f6b5..f5b3e0e431 100644 --- a/templates/skeleton/app/routes/account.orders.$id.tsx +++ b/templates/skeleton/app/routes/account.orders.$id.tsx @@ -1,4 +1,4 @@ -import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; import {Money, Image, flattenConnection} from '@shopify/hydrogen'; import type {OrderLineItemFullFragment} from 'customer-accountapi.generated'; @@ -42,13 +42,13 @@ export async function loader({params, context}: LoaderFunctionArgs) { firstDiscount?.__typename === 'PricingPercentageValue' && firstDiscount?.percentage; - return json({ + return { order, lineItems, discountValue, discountPercentage, fulfillmentStatus, - }); + }; } export default function OrderRoute() { diff --git a/templates/skeleton/app/routes/account.orders._index.tsx b/templates/skeleton/app/routes/account.orders._index.tsx index 825a6e2633..ba41d03a2c 100644 --- a/templates/skeleton/app/routes/account.orders._index.tsx +++ b/templates/skeleton/app/routes/account.orders._index.tsx @@ -4,7 +4,7 @@ import { getPaginationVariables, flattenConnection, } from '@shopify/hydrogen'; -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {CUSTOMER_ORDERS_QUERY} from '~/graphql/customer-account/CustomerOrdersQuery'; import type { CustomerOrdersFragment, @@ -34,7 +34,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { throw Error('Customer orders not found'); } - return json({customer: data.customer}); + return {customer: data.customer}; } export default function Orders() { diff --git a/templates/skeleton/app/routes/account.profile.tsx b/templates/skeleton/app/routes/account.profile.tsx index 2068e8fae5..2973758cc5 100644 --- a/templates/skeleton/app/routes/account.profile.tsx +++ b/templates/skeleton/app/routes/account.profile.tsx @@ -2,7 +2,7 @@ import type {CustomerFragment} from 'customer-accountapi.generated'; import type {CustomerUpdateInput} from '@shopify/hydrogen/customer-account-api-types'; import {CUSTOMER_UPDATE_MUTATION} from '~/graphql/customer-account/CustomerUpdateMutation'; import { - json, + data, type ActionFunctionArgs, type LoaderFunctionArgs, } from '@shopify/remix-oxygen'; @@ -26,14 +26,14 @@ export const meta: MetaFunction = () => { export async function loader({context}: LoaderFunctionArgs) { await context.customerAccount.handleAuthStatus(); - return json({}); + return {}; } export async function action({request, context}: ActionFunctionArgs) { const {customerAccount} = context; if (request.method !== 'PUT') { - return json({error: 'Method not allowed'}, {status: 405}); + return data({error: 'Method not allowed'}, {status: 405}); } const form = await request.formData(); @@ -68,12 +68,12 @@ export async function action({request, context}: ActionFunctionArgs) { throw new Error('Customer profile update failed.'); } - return json({ + return { error: null, customer: data?.customerUpdate?.customer, - }); + }; } catch (error: any) { - return json( + return data( {error: error.message, customer: null}, { status: 400, diff --git a/templates/skeleton/app/routes/account.tsx b/templates/skeleton/app/routes/account.tsx index 583e62c549..0941d4e0ef 100644 --- a/templates/skeleton/app/routes/account.tsx +++ b/templates/skeleton/app/routes/account.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {data as remixData, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Form, NavLink, Outlet, useLoaderData} from '@remix-run/react'; import {CUSTOMER_DETAILS_QUERY} from '~/graphql/customer-account/CustomerDetailsQuery'; @@ -15,7 +15,7 @@ export async function loader({context}: LoaderFunctionArgs) { throw new Error('Customer not found'); } - return json( + return remixData( {customer: data.customer}, { headers: { diff --git a/templates/skeleton/app/routes/blogs.$blogHandle.$articleHandle.tsx b/templates/skeleton/app/routes/blogs.$blogHandle.$articleHandle.tsx index 37ebb42aae..55ad500adb 100644 --- a/templates/skeleton/app/routes/blogs.$blogHandle.$articleHandle.tsx +++ b/templates/skeleton/app/routes/blogs.$blogHandle.$articleHandle.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; import {Image} from '@shopify/hydrogen'; @@ -13,7 +13,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/blogs.$blogHandle._index.tsx b/templates/skeleton/app/routes/blogs.$blogHandle._index.tsx index 9d9de5a827..a4ae80ee84 100644 --- a/templates/skeleton/app/routes/blogs.$blogHandle._index.tsx +++ b/templates/skeleton/app/routes/blogs.$blogHandle._index.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import {Image, getPaginationVariables} from '@shopify/hydrogen'; import type {ArticleItemFragment} from 'storefrontapi.generated'; @@ -15,7 +15,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/blogs._index.tsx b/templates/skeleton/app/routes/blogs._index.tsx index e5c9ce8b49..29f110fb56 100644 --- a/templates/skeleton/app/routes/blogs._index.tsx +++ b/templates/skeleton/app/routes/blogs._index.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import {getPaginationVariables} from '@shopify/hydrogen'; import {PaginatedResourceSection} from '~/components/PaginatedResourceSection'; @@ -14,7 +14,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/cart.tsx b/templates/skeleton/app/routes/cart.tsx index f477802224..86b3f08888 100644 --- a/templates/skeleton/app/routes/cart.tsx +++ b/templates/skeleton/app/routes/cart.tsx @@ -1,7 +1,7 @@ import {type MetaFunction, useLoaderData} from '@remix-run/react'; import type {CartQueryDataReturn} from '@shopify/hydrogen'; import {CartForm} from '@shopify/hydrogen'; -import {json, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; +import {data, type LoaderFunctionArgs, type ActionFunctionArgs} from '@shopify/remix-oxygen'; import {CartMain} from '~/components/CartMain'; export const meta: MetaFunction = () => { @@ -80,7 +80,7 @@ export async function action({request, context}: ActionFunctionArgs) { headers.set('Location', redirectTo); } - return json( + return data( { cart: cartResult, errors, @@ -95,7 +95,7 @@ export async function action({request, context}: ActionFunctionArgs) { export async function loader({context}: LoaderFunctionArgs) { const {cart} = context; - return json(await cart.get()); + return await cart.get(); } export default function Cart() { diff --git a/templates/skeleton/app/routes/collections.$handle.tsx b/templates/skeleton/app/routes/collections.$handle.tsx index 9dcddfb3b1..a67d46438c 100644 --- a/templates/skeleton/app/routes/collections.$handle.tsx +++ b/templates/skeleton/app/routes/collections.$handle.tsx @@ -1,4 +1,4 @@ -import {defer, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, Link, type MetaFunction} from '@remix-run/react'; import { getPaginationVariables, @@ -21,7 +21,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/collections._index.tsx b/templates/skeleton/app/routes/collections._index.tsx index f381c4b4f3..bf4f74ea85 100644 --- a/templates/skeleton/app/routes/collections._index.tsx +++ b/templates/skeleton/app/routes/collections._index.tsx @@ -1,5 +1,5 @@ import {useLoaderData, Link} from '@remix-run/react'; -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {getPaginationVariables, Image} from '@shopify/hydrogen'; import type {CollectionFragment} from 'storefrontapi.generated'; import {PaginatedResourceSection} from '~/components/PaginatedResourceSection'; @@ -11,7 +11,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/collections.all.tsx b/templates/skeleton/app/routes/collections.all.tsx index 9dd4780773..84c768bb3f 100644 --- a/templates/skeleton/app/routes/collections.all.tsx +++ b/templates/skeleton/app/routes/collections.all.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, Link, type MetaFunction} from '@remix-run/react'; import {getPaginationVariables, Image, Money} from '@shopify/hydrogen'; import type {ProductItemFragment} from 'storefrontapi.generated'; @@ -16,7 +16,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/pages.$handle.tsx b/templates/skeleton/app/routes/pages.$handle.tsx index 1b01769a79..3a75708514 100644 --- a/templates/skeleton/app/routes/pages.$handle.tsx +++ b/templates/skeleton/app/routes/pages.$handle.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; export const meta: MetaFunction = ({data}) => { @@ -12,7 +12,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/policies.$handle.tsx b/templates/skeleton/app/routes/policies.$handle.tsx index 367d4c431b..941c263217 100644 --- a/templates/skeleton/app/routes/policies.$handle.tsx +++ b/templates/skeleton/app/routes/policies.$handle.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {Link, useLoaderData, type MetaFunction} from '@remix-run/react'; import {type Shop} from '@shopify/hydrogen/storefront-api-types'; @@ -38,7 +38,7 @@ export async function loader({params, context}: LoaderFunctionArgs) { throw new Response('Could not find the policy', {status: 404}); } - return json({policy}); + return {policy}; } export default function Policy() { diff --git a/templates/skeleton/app/routes/policies._index.tsx b/templates/skeleton/app/routes/policies._index.tsx index 243ee1fa52..bc1e43347b 100644 --- a/templates/skeleton/app/routes/policies._index.tsx +++ b/templates/skeleton/app/routes/policies._index.tsx @@ -1,4 +1,4 @@ -import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, Link} from '@remix-run/react'; export async function loader({context}: LoaderFunctionArgs) { @@ -9,7 +9,7 @@ export async function loader({context}: LoaderFunctionArgs) { throw new Response('No policies found', {status: 404}); } - return json({policies}); + return {policies}; } export default function Policies() { diff --git a/templates/skeleton/app/routes/products.$handle.tsx b/templates/skeleton/app/routes/products.$handle.tsx index 11e495cbac..0028b423de 100644 --- a/templates/skeleton/app/routes/products.$handle.tsx +++ b/templates/skeleton/app/routes/products.$handle.tsx @@ -1,4 +1,4 @@ -import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {type LoaderFunctionArgs} from '@shopify/remix-oxygen'; import {useLoaderData, type MetaFunction} from '@remix-run/react'; import { getSelectedProductOptions, @@ -29,7 +29,7 @@ export async function loader(args: LoaderFunctionArgs) { // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); - return defer({...deferredData, ...criticalData}); + return {...deferredData, ...criticalData}; } /** diff --git a/templates/skeleton/app/routes/search.tsx b/templates/skeleton/app/routes/search.tsx index 2e453c221e..c423960f08 100644 --- a/templates/skeleton/app/routes/search.tsx +++ b/templates/skeleton/app/routes/search.tsx @@ -1,5 +1,4 @@ import { - json, type LoaderFunctionArgs, type ActionFunctionArgs, } from '@shopify/remix-oxygen'; @@ -29,7 +28,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { return {term: '', result: null, error: error.message}; }); - return json(await searchPromise); + return await searchPromise; } /** diff --git a/templates/skeleton/guides/predictiveSearch/predictiveSearch.md b/templates/skeleton/guides/predictiveSearch/predictiveSearch.md index 3f3d45835a..21ad9e6243 100644 --- a/templates/skeleton/guides/predictiveSearch/predictiveSearch.md +++ b/templates/skeleton/guides/predictiveSearch/predictiveSearch.md @@ -194,7 +194,7 @@ async function predictiveSearch({ const total = Object.values(items).reduce((acc, {length}) => acc + length, 0); - return json({term, result: {items, total}, error: null}); + return {term, result: {items, total}, error: null}; } ``` @@ -217,7 +217,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { const isPredictive = url.searchParams.has('predictive'); if (!isPredictive) { - return json({}) + return {} } const searchPromise = predictiveSearch({request, context}) @@ -227,7 +227,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { return {term: '', result: null, error: error.message}; }); - return json(await searchPromise); + return await searchPromise; } ``` diff --git a/templates/skeleton/guides/search/search.md b/templates/skeleton/guides/search/search.md index 5925d5e017..c4d695090c 100644 --- a/templates/skeleton/guides/search/search.md +++ b/templates/skeleton/guides/search/search.md @@ -191,7 +191,7 @@ async function search({ return acc + nodes.length; }, 0); - return json({term, result: {total, items}}); + return {term, result: {total, items}}; } ``` @@ -212,7 +212,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { const isRegular = !url.searchParams.has('predictive'); if (!isRegular) { - return json({}) + return {} } const searchPromise = regularSearch({request, context}); @@ -222,7 +222,7 @@ export async function loader({request, context}: LoaderFunctionArgs) { return {term: '', result: null, error: error.message}; }); - return json(await searchPromise); + return await searchPromise; } ``` diff --git a/templates/skeleton/vite.config.ts b/templates/skeleton/vite.config.ts index 5ee9b626fb..56de3011d7 100644 --- a/templates/skeleton/vite.config.ts +++ b/templates/skeleton/vite.config.ts @@ -4,6 +4,12 @@ import {oxygen} from '@shopify/mini-oxygen/vite'; import {vitePlugin as remix} from '@remix-run/dev'; import tsconfigPaths from 'vite-tsconfig-paths'; +declare module "@remix-run/server-runtime" { + interface Future { + v3_singleFetch: true; + } +} + export default defineConfig({ plugins: [ hydrogen(), @@ -15,6 +21,7 @@ export default defineConfig({ v3_relativeSplatPath: true, v3_throwAbortReason: true, v3_lazyRouteDiscovery: true, + v3_singleFetch: true, }, }), tsconfigPaths(),