|
1 | 1 | # skeleton |
2 | 2 |
|
| 3 | +## 2025.4.1 |
| 4 | + |
| 5 | +### Patch Changes |
| 6 | + |
| 7 | +- Add a new tokenless Storefront API route to the starter template. ([#2948](https://github.com/Shopify/hydrogen/pull/2948)) by [@blittle](https://github.com/blittle) |
| 8 | + |
| 9 | +- Updated dependencies [[`d44fab3d`](https://github.com/Shopify/hydrogen/commit/d44fab3d07c5e255edcd22745d4d7e2db2c3e60f)]: |
| 10 | + - @shopify/hydrogen@2025.4.1 |
| 11 | + |
3 | 12 | ## 2025.4.0 |
4 | 13 |
|
5 | 14 | ### Patch Changes |
|
164 | 173 | 1. Add a routes.ts file. This is your new Remix route configuration file. |
165 | 174 |
|
166 | 175 | ```ts |
167 | | - import {flatRoutes} from '@remix-run/fs-routes'; |
168 | | - import {layout, type RouteConfig} from '@remix-run/route-config'; |
169 | | - import {hydrogenRoutes} from '@shopify/hydrogen'; |
| 176 | + import { flatRoutes } from "@remix-run/fs-routes"; |
| 177 | + import { layout, type RouteConfig } from "@remix-run/route-config"; |
| 178 | + import { hydrogenRoutes } from "@shopify/hydrogen"; |
170 | 179 |
|
171 | 180 | export default hydrogenRoutes([ |
172 | 181 | // Your entire app reading from routes folder using Layout from layout.tsx |
173 | | - layout('./layout.tsx', await flatRoutes()), |
| 182 | + layout("./layout.tsx", await flatRoutes()), |
174 | 183 | ]) satisfies RouteConfig; |
175 | 184 | ``` |
176 | 185 |
|
|
761 | 770 | 8. Update the `ProductForm` component. |
762 | 771 |
|
763 | 772 | ```tsx |
764 | | - import {Link, useNavigate} from '@remix-run/react'; |
765 | | - import {type MappedProductOptions} from '@shopify/hydrogen'; |
| 773 | + import { Link, useNavigate } from "@remix-run/react"; |
| 774 | + import { type MappedProductOptions } from "@shopify/hydrogen"; |
766 | 775 | import type { |
767 | 776 | Maybe, |
768 | 777 | ProductOptionValueSwatch, |
769 | | - } from '@shopify/hydrogen/storefront-api-types'; |
770 | | - import {AddToCartButton} from './AddToCartButton'; |
771 | | - import {useAside} from './Aside'; |
772 | | - import type {ProductFragment} from 'storefrontapi.generated'; |
| 778 | + } from "@shopify/hydrogen/storefront-api-types"; |
| 779 | + import { AddToCartButton } from "./AddToCartButton"; |
| 780 | + import { useAside } from "./Aside"; |
| 781 | + import type { ProductFragment } from "storefrontapi.generated"; |
773 | 782 |
|
774 | 783 | export function ProductForm({ |
775 | 784 | productOptions, |
776 | 785 | selectedVariant, |
777 | 786 | }: { |
778 | 787 | productOptions: MappedProductOptions[]; |
779 | | - selectedVariant: ProductFragment['selectedOrFirstAvailableVariant']; |
| 788 | + selectedVariant: ProductFragment["selectedOrFirstAvailableVariant"]; |
780 | 789 | }) { |
781 | 790 | const navigate = useNavigate(); |
782 | | - const {open} = useAside(); |
| 791 | + const { open } = useAside(); |
783 | 792 | return ( |
784 | 793 | <div className="product-form"> |
785 | 794 | {productOptions.map((option) => ( |
|
813 | 822 | to={`/products/${handle}?${variantUriQuery}`} |
814 | 823 | style={{ |
815 | 824 | border: selected |
816 | | - ? '1px solid black' |
817 | | - : '1px solid transparent', |
| 825 | + ? "1px solid black" |
| 826 | + : "1px solid transparent", |
818 | 827 | opacity: available ? 1 : 0.3, |
819 | 828 | }} |
820 | 829 | > |
|
831 | 840 | <button |
832 | 841 | type="button" |
833 | 842 | className={`product-options-item${ |
834 | | - exists && !selected ? ' link' : '' |
| 843 | + exists && !selected ? " link" : "" |
835 | 844 | }`} |
836 | 845 | key={option.name + name} |
837 | 846 | style={{ |
838 | 847 | border: selected |
839 | | - ? '1px solid black' |
840 | | - : '1px solid transparent', |
| 848 | + ? "1px solid black" |
| 849 | + : "1px solid transparent", |
841 | 850 | opacity: available ? 1 : 0.3, |
842 | 851 | }} |
843 | 852 | disabled={!exists} |
|
861 | 870 | <AddToCartButton |
862 | 871 | disabled={!selectedVariant || !selectedVariant.availableForSale} |
863 | 872 | onClick={() => { |
864 | | - open('cart'); |
| 873 | + open("cart"); |
865 | 874 | }} |
866 | 875 | lines={ |
867 | 876 | selectedVariant |
|
875 | 884 | : [] |
876 | 885 | } |
877 | 886 | > |
878 | | - {selectedVariant?.availableForSale ? 'Add to cart' : 'Sold out'} |
| 887 | + {selectedVariant?.availableForSale ? "Add to cart" : "Sold out"} |
879 | 888 | </AddToCartButton> |
880 | 889 | </div> |
881 | 890 | ); |
|
898 | 907 | aria-label={name} |
899 | 908 | className="product-option-label-swatch" |
900 | 909 | style={{ |
901 | | - backgroundColor: color || 'transparent', |
| 910 | + backgroundColor: color || "transparent", |
902 | 911 | }} |
903 | 912 | > |
904 | 913 | {!!image && <img src={image} alt={name} />} |
|
1399 | 1408 | New `withCache.fetch` is for caching simple fetch requests. This method caches the responses if they are OK responses, and you can pass `shouldCacheResponse`, `cacheKey`, etc. to modify behavior. `data` is the consumed body of the response (we need to consume to cache it). |
1400 | 1409 |
|
1401 | 1410 | ```ts |
1402 | | - const withCache = createWithCache({cache, waitUntil, request}); |
| 1411 | + const withCache = createWithCache({ cache, waitUntil, request }); |
1403 | 1412 |
|
1404 | | - const {data, response} = await withCache.fetch<{data: T; error: string}>( |
1405 | | - 'my-cms.com/api', |
| 1413 | + const { data, response } = await withCache.fetch<{ data: T; error: string }>( |
| 1414 | + "my-cms.com/api", |
1406 | 1415 | { |
1407 | | - method: 'POST', |
1408 | | - headers: {'Content-type': 'application/json'}, |
| 1416 | + method: "POST", |
| 1417 | + headers: { "Content-type": "application/json" }, |
1409 | 1418 | body, |
1410 | 1419 | }, |
1411 | 1420 | { |
1412 | 1421 | cacheStrategy: CacheLong(), |
1413 | 1422 | // Cache if there are no data errors or a specific data that make this result not suited for caching |
1414 | 1423 | shouldCacheResponse: (result) => !result?.error, |
1415 | | - cacheKey: ['my-cms', body], |
1416 | | - displayName: 'My CMS query', |
| 1424 | + cacheKey: ["my-cms", body], |
| 1425 | + displayName: "My CMS query", |
1417 | 1426 | }, |
1418 | 1427 | ); |
1419 | 1428 | ``` |
|
1989 | 1998 |
|
1990 | 1999 | ```tsx |
1991 | 2000 | // app/lib/root-data.ts |
1992 | | - import {useMatches} from '@remix-run/react'; |
1993 | | - import type {SerializeFrom} from '@shopify/remix-oxygen'; |
1994 | | - import type {loader} from '~/root'; |
| 2001 | + import { useMatches } from "@remix-run/react"; |
| 2002 | + import type { SerializeFrom } from "@shopify/remix-oxygen"; |
| 2003 | + import type { loader } from "~/root"; |
1995 | 2004 |
|
1996 | 2005 | /** |
1997 | 2006 | * Access the result of the root loader from a React component. |
|
2153 | 2162 | - This is an important fix to a bug with 404 routes and path-based i18n projects where some unknown routes would not properly render a 404. This fixes all new projects, but to fix existing projects, add a `($locale).tsx` route with the following contents: ([#1732](https://github.com/Shopify/hydrogen/pull/1732)) by [@blittle](https://github.com/blittle) |
2154 | 2163 |
|
2155 | 2164 | ```ts |
2156 | | - import {type LoaderFunctionArgs} from '@remix-run/server-runtime'; |
| 2165 | + import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; |
2157 | 2166 |
|
2158 | | - export async function loader({params, context}: LoaderFunctionArgs) { |
2159 | | - const {language, country} = context.storefront.i18n; |
| 2167 | + export async function loader({ params, context }: LoaderFunctionArgs) { |
| 2168 | + const { language, country } = context.storefront.i18n; |
2160 | 2169 |
|
2161 | 2170 | if ( |
2162 | 2171 | params.locale && |
2163 | 2172 | params.locale.toLowerCase() !== `${language}-${country}`.toLowerCase() |
2164 | 2173 | ) { |
2165 | 2174 | // If the locale URL param is defined, yet we still are still at the default locale |
2166 | 2175 | // then the the locale param must be invalid, send to the 404 page |
2167 | | - throw new Response(null, {status: 404}); |
| 2176 | + throw new Response(null, { status: 404 }); |
2168 | 2177 | } |
2169 | 2178 |
|
2170 | 2179 | return null; |
|
2220 | 2229 | ```yaml |
2221 | 2230 | projects: |
2222 | 2231 | default: |
2223 | | - schema: 'node_modules/@shopify/hydrogen/storefront.schema.json' |
| 2232 | + schema: "node_modules/@shopify/hydrogen/storefront.schema.json" |
2224 | 2233 | documents: |
2225 | | - - '!*.d.ts' |
2226 | | - - '*.{ts,tsx,js,jsx}' |
2227 | | - - 'app/**/*.{ts,tsx,js,jsx}' |
| 2234 | + - "!*.d.ts" |
| 2235 | + - "*.{ts,tsx,js,jsx}" |
| 2236 | + - "app/**/*.{ts,tsx,js,jsx}" |
2228 | 2237 | ``` |
2229 | 2238 |
|
2230 | 2239 | - Improve resiliency of `HydrogenSession` ([#1583](https://github.com/Shopify/hydrogen/pull/1583)) by [@blittle](https://github.com/blittle) |
|
2439 | 2448 | ```ts |
2440 | 2449 | // root.tsx |
2441 | 2450 |
|
2442 | | - import {useMatches} from '@remix-run/react'; |
2443 | | - import {type SerializeFrom} from '@shopify/remix-oxygen'; |
| 2451 | + import { useMatches } from "@remix-run/react"; |
| 2452 | + import { type SerializeFrom } from "@shopify/remix-oxygen"; |
2444 | 2453 |
|
2445 | 2454 | export const useRootLoaderData = () => { |
2446 | 2455 | const [root] = useMatches(); |
|
0 commit comments