-
Add optional headers param for logout redirect (#2602) by @coryagami
-
Stabilize
getSitemap
,getSitemapIndex
and implement on skeleton (#2589) by @juanpprieto- Update the
getSitemapIndex
at/app/routes/[sitemap.xml].tsx
- import {unstable__getSitemapIndex as getSitemapIndex} from '@shopify/hydrogen'; + import {getSitemapIndex} from '@shopify/hydrogen';
- Update the
getSitemap
at/app/routes/sitemap.$type.$page[.xml].tsx
- import {unstable__getSitemap as getSitemap} from '@shopify/hydrogen'; + import {getSitemap} from '@shopify/hydrogen';
For a reference implementation please see the skeleton template sitemap routes
- Update the
-
Update
<ProductPrice>
to remove deprecated code usage forpriceV2
andcompareAtPriceV2
. Remove export forgetCustomerPrivacy
. (#2601) by @wizardlyhel -
[Breaking change] (#2588) by @wizardlyhel
Set up Customer Privacy without the Shopify's cookie banner by default.
If you are using Shopify's cookie banner to handle user consent in your app, you need to set
withPrivacyBanner: true
to the consent config. Without this update, the Shopify cookie banner will not appear.return defer({ ... consent: { checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN, storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN, + withPrivacyBanner: true, // localize the privacy banner country: args.context.storefront.i18n.country, language: args.context.storefront.i18n.language, }, });
-
Update to 2024-10 SFAPI (#2570) by @wizardlyhel
-
[Breaking change] (#2546) by @frandiox
Update
createWithCache
to make it harder to accidentally cache undesired results.request
is now mandatory prop when initializingcreateWithCache
.// server.ts export default { async fetch( request: Request, env: Env, executionContext: ExecutionContext, ): Promise<Response> { try { // ... - const withCache = createWithCache({cache, waitUntil}); + const withCache = createWithCache({cache, waitUntil, request});
createWithCache
now returns an object with two utility functions:withCache.run
andwithCache.fetch
. Both have a new propshouldCacheResult
that must be defined.The original
withCache
callback function is nowwithCache.run
. This is useful to run multiple fetch calls and merge their responses, or run any arbitrary code. It caches anything you return, but you can throw if you don't want to cache anything.const withCache = createWithCache({cache, waitUntil, request}); const fetchMyCMS = (query) => { - return withCache(['my-cms', query], CacheLong(), async (params) => { + return withCache.run({ + cacheKey: ['my-cms', query], + cacheStrategy: CacheLong(), + // Cache if there are no data errors or a specific data that make this result not suited for caching + shouldCacheResult: (result) => !result?.errors, + }, async(params) => { const response = await fetch('my-cms.com/api', { method: 'POST', body: query, }); if (!response.ok) throw new Error(response.statusText); const {data, error} = await response.json(); if (error || !data) throw new Error(error ?? 'Missing data'); params.addDebugData({displayName: 'My CMS query', response}); return data; }); };
New
withCache.fetch
is for caching simple fetch requests. This method caches the responses if they are OK responses, and you can passshouldCacheResponse
,cacheKey
, etc. to modify behavior.data
is the consumed body of the response (we need to consume to cache it).const withCache = createWithCache({cache, waitUntil, request}); const {data, response} = await withCache.fetch<{data: T; error: string}>( 'my-cms.com/api', { method: 'POST', headers: {'Content-type': 'application/json'}, body, }, { cacheStrategy: CacheLong(), // Cache if there are no data errors or a specific data that make this result not suited for caching shouldCacheResponse: (result) => !result?.error, cacheKey: ['my-cms', body], displayName: 'My CMS query', }, );
-
[Breaking change] (#2585) by @wizardlyhel
Deprecate usages of
product.options.values
and useproduct.options.optionValues
instead.- Update your product graphql query to use the new
optionValues
field.
const PRODUCT_FRAGMENT = `#graphql fragment Product on Product { id title options { name - values + optionValues { + name + } }
- Update your
<VariantSelector>
to use the newoptionValues
field.
<VariantSelector handle={product.handle} - options={product.options.filter((option) => option.values.length > 1)} + options={product.options.filter((option) => option.optionValues.length > 1)} variants={variants} >
- Update your product graphql query to use the new
-
Add utility functions
decodeEncodedVariant
andisOptionValueCombinationInEncodedVariant
for parsingproduct.encodedVariantExistence
andproduct.encodedVariantAvailability
fields. (#2425) by @lhoffbeck -
[Breaking change] (#2572) by @wizardlyhel
Update all cart mutation methods from
createCartHandler
to return cart warnings.As of API version 2024-10, inventory errors about stock levels will no longer be included in the
userErrors
of cart mutations. Inventory errors will now be available in a new return fieldwarnings
and will contain explicit code values ofMERCHANDISE_NOT_ENOUGH_STOCK
orMERCHANDISE_OUT_OF_STOCK
. Reference: https://shopify.dev/changelog/cart-warnings-in-storefront-api-cart -
Updated dependencies [
8c89f298
,84a66b1e
,76cd4f9b
]:- @shopify/[email protected]
-
Fix provided canTrack function being override by ShopifyAnalytics (#2596) by @wizardlyhel
-
Fix analytics provider breaking after hitting a 404 page (#2590) by @wizardlyhel
-
Updated dependencies [
229d4aa0
]:- @shopify/[email protected]
-
Update customer account buyer authentication exchange (#2437) by @s-lee-kwong
-
Updated dependencies [
bb5b0979
]:- @shopify/[email protected]
- Fix Shopify.customerPrivacy.setTrackingConsent override not working properly (#2538) by @wizardlyhel
-
Emit a document event
shopifyCustomerPrivacyApiLoaded
when Customer Privacy API is ready and fix analytics events sending to Shopify. (#2528) by @wizardlyhel -
Updated dependencies [
d0ff37a9
]:- @shopify/[email protected]
-
Prevent CSP nonces from persisting between requests (#2500) by @juanpprieto
-
useOptimisticCart: optimistically calculate totalQuantity (#2459) by @scottdixon
-
createCartHandler supplies updateGiftCardCodes method (#2298) by @wizardlyhel
-
Introduce a new abstraction for generating sitemap index and child sitemaps. (#2478) by @blittle
See the sitemap example for how to use it and read the docs for more information.
-
Add localization support to consent privacy banner (#2457) by @juanpprieto
-
Updated dependencies [
ca0c7692
,81f2b540
]:- @shopify/[email protected]
- Fix the
Script
component to not throw when using it for inline scripts withdangerouslySetInnerHTML
(#2428) by @blittle
-
Prevent sending analytics data to Shopify when Chrome-Lighthouse user agent is detected (#2401) by @wizardlyhel
-
Create
createHydrogenContext
that combinedcreateStorefrontClient
,createCustomerAccountClient
andcreateCartHandler
. (#2333) by @michenly -
Add a
waitForHydration
prop to theScript
component to delay loading until after hydration. This fixes third-party scripts that modify the DOM and cause hydration errors. (#2389) by @blittleNote: For security,
nonce
is not supported when usingwaitForHydration
. Instead you need to add the domain of the script directly to your Content Securitiy Policy directives. -
Fix the
OptimisticCart
type to properly retain the generic of line items. TheOptimisticCartLine
type now takes a cart or cart line item generic. (#2327) by @blittle -
Export
ShopAnalytics
type (#2384) by @Braedencraig -
Updated dependencies [
cfbfc827
,b09e9a4c
]:- @shopify/[email protected]
-
[Breaking change] (#2137) by @michenly
customerAccount
no longer commit session automatically. -
[Breaking change] (#2113) by @blittle
Previously the
VariantSelector
component would filter out options that only had one value. This is undesireable for some apps. We've removed that filter, if you'd like to retain the existing functionality, simply filter the options prop before it is passed to theVariantSelector
component:<VariantSelector handle={product.handle} + options={product.options.filter((option) => option.values.length > 1)} - options={product.options} variants={variants}> </VariantSelector>
Fixes #1198
-
Improve the types for
useOptimisticCart()
(#2269) by @blittle -
Fix a small rounding issue when checking stale-while-revalidate timing. (#2220) by @frandiox
-
Update virtual route to use Layout component in the root file. (#2292) by @michenly
-
Add
sellingPlanId
support toBuyNowButton
. (#2254) by @dvisockas -
Fix customData from Analytics.Provider not being passed to page view events (#2224) by @wizardlyhel
-
Auto cookie domain detection for customer privacy api and better error message for missing analytics fields (#2256) by @wizardlyhel
-
[New Features] (#2183) by @blittle
Add a
useOptimisticVariant
hook for optimistically rendering product variant changes. This makes switching product variants instantaneous. Example usage:function Product() { const {product, variants} = useLoaderData<typeof loader>(); // The selectedVariant optimistically changes during page // transitions with one of the preloaded product variants const selectedVariant = useOptimisticVariant( product.selectedVariant, variants, ); return <ProductMain selectedVariant={selectedVariant} />; }
This also introduces a small breaking change to the
VariantSelector
component, which now immediately updates which variant is active. If you'd like to retain the current functionality, and have theVariantSelector
wait for the page navigation to complete before updating, use thewaitForNavigation
prop:<VariantSelector handle={product.handle} options={product.options} waitForNavigation > ... </VariantSelector>
-
Return
null
instead of empty object fromcart.get()
when the cart id is invalid. (#2258) by @frandiox -
Updated dependencies [
54c2f7ad
]:- @shopify/[email protected]
-
Add the
useOptimisticCart()
hook. This hook takes the cart object as a parameter, and processes all pending cart actions, locally mutating the cart with optimistic state. An optimistic cart makes cart actions immediately render in the browser while the action syncs to the server. This increases the perceived performance of the application. (#2069) by @blittleExample usage:
// Root loader returns the cart data export async function loader({context}: LoaderFunctionArgs) { return defer({ cart: context.cart.get(), }); } // The cart component renders each line item in the cart. export function Cart({cart}) { if (!cart?.lines?.nodes?.length) return <p>Nothing in cart</p>; return cart.lines.nodes.map((line) => ( <div key={line.id}> <Link to={`/products${line.merchandise.product.handle}`}> {line.merchandise.product.title} </Link> </div> )); }
The problem with this code is that it can feel slow. If a new item is added to the cart, it won't render until the server action completes and the client revalidates the root loader with the new cart data.
If we update the cart implementation with a new
useOptimisticCart()
hook, Hydrogen can take the pending add to cart action, and apply it locally with the existing cart data:export function Cart({cart}) { const optimisticCart = useOptimisticCart(cart); if (!optimisticCart?.lines?.nodes?.length) return <p>Nothing in cart</p>; return optimisticCart.lines.nodes.map((line) => ( <div key={line.id}> <Link to={`/products${line.merchandise.product.handle}`}> {line.merchandise.product.title} </Link> </div> )); }
This works automatically with the
CartForm.ACTIONS.LinesUpdate
andCartForm.ACTIONS.LinesRemove
. To make it work withCartForm.Actions.LinesAdd
, update theCartForm
to include theselectedVariant
:export function ProductCard({product}) { return ( <div> <h2>{product.title}</h2> <CartForm route="/cart" action={CartForm.ACTIONS.LinesAdd} inputs={{ lines: [ { merchandiseId: product.selectedVariant.id, quantity: 1, // The whole selected variant is not needed on the server, used in // the client to render the product until the server action resolves selectedVariant: product.selectedVariant, }, ], }} > <button type="submit">Add to cart</button> </CartForm> </div> ); }
Sometimes line items need to render differently when they have yet to process on the server. A new isOptimistic flag is added to each line item:
export function Cart({cart}) { const optimisticCart = useOptimisticCart(cart); if (!cart?.lines?.nodes?.length) return <p>Nothing in cart</p>; return optimisticCart.lines.nodes.map((line) => ( <div key={line.id} style={{opacity: line.isOptimistic ? 0.8 : 1}}> <Link to={`/products${line.merchandise.product.handle}`}> {line.merchandise.product.title} </Link> <CartForm route="/cart" action={CartForm.ACTIONS.LinesRemove} inputs={{lineIds}} disabled={line.isOptimistic} > <button type="submit">Remove</button> </CartForm> </div> )); }
-
Adds type support for the script-src-elem directive for CSPs (#2105) by @altonchaney
-
Fix
storefrontRedirect
to strip trailing slashes when querying for redirects. Resolves #2090 (#2110) by @blittle -
Ignore
/favicon.ico
route in Subrequest Profiler. (#2180) by @frandiox -
Improve errors when a CJS dependency needs to be added to Vite's ssr.optimizeDeps.include. (#2106) by @frandiox
-
<Analytics>
anduseAnalytics
are now stable. (#2141) by @wizardlyhel -
Improve VariantSelector to return variant object in option values. Thank you @NabeelAhmed1721 by @blittle
-
Fix: Use exiting
id_token
during Customer Account API token refresh because it does not get return in the API. (#2103) by @juanpprieto -
Updated dependencies [
73716c88
,30d18bdb
]:- @shopify/[email protected]
-
Add JSdoc to
getSelectedProductOptions
utility and cleanup the skeleton implementation (#2089) by @juanpprieto -
Adding support for B2B to the customer account client and cart handler to store and manage buyer context. Currently Unstable. (#1886) by @dustinfirman
-
When extending the content security policy, if the default directive is 'none' then the default won't be merged into the final directive. (#2076) by @nkgentile
-
Update
content-security-policy-builder
subdependency to ESM version to avoid preprocessing in Vite. (#2057) by @frandiox -
Fix Analytics. Provider for error checking and working without privacy banner. (#2025) by @wizardlyhel
-
Updated dependencies [
081e1498
]:- @shopify/[email protected]
-
Warn when using the deprecated Seo component (#1983) by @blittle
-
Fix names and URLs shown for HIT/STALE items in the Subrequest Profiler. (#2021) by @frandiox
-
Updated dependencies [
0f5cab00
]:- @shopify/[email protected]
-
Change
storefrontRedirect
to ignore query parameters when matching redirects. For example, a redirect in the admin from/snowboards
to/collections/snowboards
will now match on the URL/snowboards?utm_campaign=buffer
and redirect the user to/collections/snowboards?utm_campaign=buffer
. (#1900) by @blittleThis is a breaking change. If you want to retain the legacy functionality that is query parameter sensitive, pass
matchQueryParams
tostorefrontRedirect()
:storefrontRedirect({ request, response, storefront, matchQueryParams: true, });
-
Make
StorefrontRedirect
case insensitive when querying redirect URLs from the Storefront API. (#1941) by @blittle -
Fix bug where
storefrontRedirect
would return an error on soft page navigations. (#1880) by @blittle -
Fix a bug where
cart
could be null, even though a new cart was created by adding a line item. (#1865) by @blittleThis allows calling the cart
.get()
method right after creating a new cart with one of the mutation methods:create()
,addLines()
,updateDiscountCodes()
,updateBuyerIdentity()
,updateNote()
,updateAttributes()
,setMetafields()
.import { createCartHandler, cartGetIdDefault, cartSetIdDefault, } from '@shopify/hydrogen'; const cartHandler = createCartHandler({ storefront, getCartId: cartGetIdDefault(request.headers), setCartId: cartSetIdDefault(), cartQueryFragment: CART_QUERY_FRAGMENT, cartMutateFragment: CART_MUTATE_FRAGMENT, }); await cartHandler.addLines([{merchandiseId: '...'}]); // .get() now returns the cart as expected const cart = await cartHandler.get();
-
Add
postLogoutRedirectUri
option to the Customer Account API client's logout method. (#1871) by @michenly -
Introducing
<UNSTABLE_Analytics.Provider>
that also includes Shopify analytics, Customer Privacy API and Privacy banner (#1789) by @wizardlyhel -
Export new Hydrogen Vite plugin from
@shopify/hydrogen/vite
. (#1935) by @frandiox -
Add the
customer-account push
command to the Hydrogen CLI. This allows you to push the current--dev-origin
URL to the Shopify admin to enable secure connection to the Customer Account API for local development. (#1804) by @michenly -
Fix default content security policy directive for
frameAncestors
. (#1883) by @blittle -
Fall back to "mock.shop" when no value is passed in
storeDomain
tocreateStorefrontClient
in development. (#1971) by @frandiox -
Allow
ui_locale
to be passed to the customer account login page. (#1842) by @wizardlyhel -
Deprecate the
<Seo />
component in favor of directly using Remix meta route exports. Add thegetSeoMeta
to make migration easier. (#1875) by @blittle1. Remove the
<Seo />
component fromroot.jsx
:export default function App() { const nonce = useNonce(); const data = useLoaderData<typeof loader>(); return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> - <Seo /> <Meta /> <Links /> </head> <body> <Layout {...data}> <Outlet /> </Layout> <ScrollRestoration nonce={nonce} /> <Scripts nonce={nonce} /> <LiveReload nonce={nonce} /> </body> </html> ); }
2. Add a Remix meta export to each route that returns an
seo
property from aloader
orhandle
:+import {getSeoMeta} from '@shopify/hydrogen'; export async function loader({context}) { const {shop} = await context.storefront.query(` query layout { shop { name description } } `); return { seo: { title: shop.title, description: shop.description, }, }; } +export const meta = ({data}) => { + return getSeoMeta(data.seo); +};
3. Merge root route meta data
If your root route loader also returns an
seo
property, make sure to merge that data:export const meta = ({data, matches}) => { return getSeoMeta( matches[0].data.seo, // the current route seo data overrides the root route data data.seo, ); };
Or more simply:
export const meta = ({data, matches}) => { return getSeoMeta(...matches.map((match) => match.data.seo)); };
4. Override meta
Sometimes
getSeoMeta
might produce a property in a way you'd like to change. Map over the resulting array to change it. For example, Hydrogen removes query parameters from canonical URLs, add them back:export const meta = ({data, location}) => { return getSeoMeta(data.seo).map((meta) => { if (meta.rel === 'canonical') { return { ...meta, href: meta.href + location.search, }; } return meta; }); };
-
Updated dependencies [
f4d6e5b0
,a209019f
,e50f4349
]:- @shopify/[email protected]
-
add optional fetcher key to CartForm (#1792) by @rmiller61
-
Fix XSS vulnerability in the SEO component (#1839) by @blittle
- Fix
PreviousLink
andNextLink
types in the Pagination component. (#1774) by @lorenzo-del-rosario
-
🐛 Fix issue where customer login does not persist to checkout (#1719) by @michenly
✨ Add
customerAccount
option tocreateCartHandler
. Where a?logged_in=true
will be added to the checkoutUrl for cart query if a customer is logged in. -
Customer Account API client's
query
&mutate
method now returnserrors
as an array of GraphQLError(s) that is better formatted. (#1765) by @michenlyLog GraphQL errors automatically in Customer Account API client, with a new
logErrors: boolean
option to disable it. -
Updated dependencies [
409e1bca
]:- @shopify/[email protected]
-
Add support for multiple schemas in GraphiQL. Fix links in Subrequest Profiler. (#1693) by @frandiox
-
♻️
CustomerClient
type is deprecated and replaced byCustomerAccount
(#1692) by @michenly -
Log GraphQL errors automatically in Storefront client, with a new
logErrors: boolean
option to disable it. Add back a link to GraphiQL in the error message. (#1690) by @frandiox
-
Better Hydrogen error handling (#1645) by @wizardlyhel
- Fix storefront client throwing on partial successful errors
- Fix subrequest profiler to better display network errors with URL information for Storefront API requests
This update changes the shape of the error objects returned by the
createCartHandler
method.Previously, mutations could return an
errors
array that contained auserErrors
array.With this change, these arrays are no longer nested. The response can contain both an
errors
array and auserErrors
array.errors
contains GraphQL execution errors.userErrors
contains errors caused by the cart mutation itself (such as adding a product that has zero inventory).storefront.isApiError
is deprecated.cart.get()
used to return aCart
type. Now it returnsCartReturn
type to accommodate theerrors
object.- All other
cart
methods (ie.cart.addLines
) used to return aCartQueryData
type. Now it returnsCartQueryDataReturn
type to accommodate theerrors
object.
-
Custom rules passed to
createContentSecurityPolicy
now extend the default Shopify and development domains, instead of overriding them (#1593) by @michenly -
Upgrade to Storefront API v2024-01 (#1642) by @wizardlyhel
-
Add Subrequest Profiler developer tool to enable better observability of server-side network requests and caching behaviors (#1511) by @wizardlyhel
-
Introduce the new
createCustomerAccountClient
for interacting with the Customer Account API (#1606) by @michenly
-
Fix a bug that allowed undesired redirect to external domains (#1629) by @wizardlyhel
-
Fix content security policy to recognize
localhost
asset server as a valid source when running thedev
command (#1591) by @michenly -
Fix the
<Seo />
component to render canonical URLs without trailing slashes. Thanks to @joshuafredrickson for reporting (#1622) by @blittle -
Make default
HydrogenSession
type extensible. (#1590) by @michenlyUpdate implementation of HydrogenSession using type:
import { + type HydrogenSession, } from '@shopify/hydrogen'; - class HydrogenSession { + class AppSession implements HydrogenSession { ... }
-
Fix error stack traces thrown by API clients if promises are not awaited (#1656) by @frandiox
-
Updated dependencies [
0629bc77
,dc8f90de
,ca1161b2
]:- @shopify/[email protected]
-
Fix the Pagination component to always restore scroll correctly on back/forth navigation. (#1508) by @blittle
-
Serve assets from a separate domain when running the dev server, to better simulate cross-domain behaviors. This makes it more realistic to work with CORS requests, content security policies, and CDN paths in development. (#1503) by @frandiox
-
Export caching types to make creating custom clients easier in TypeScript. (#1507) by @juanpprieto
-
Update the return types of the Customer Account API query and mutation methods. Also update Customer Account API default version to 2024-01. (#1537) by @blittle
-
Fix how peer dependencies are resolved. (#1489) by @frandiox
-
Add default
channel
value ofhydrogen
to Hydrogen’sShopPayButton
component. (#1447) by @QuintonC -
Updated dependencies [
848c6260
,62f67873
,e8cc49fe
]:- @shopify/[email protected]
- Change @remix-run/server-runtime to properly be a peer dependency by @blittle
- SEO component: remove URL params from canonical tags (#1478) by @scottdixon
Hydrogen 2023-10 has upgraded to Remix v2 and is now a peer dependency.
-
Please check the Remix v2 release notes to see what needs to be changed in your app code. Common changes include:
- Renaming types prefixed with
V2_
. For example,V2_MetaFunction
is nowMetaFunction
. - Renaming other types like
LoaderArgs
andActionArgs
, which are nowLoaderFunctionArgs
andActionFunctionArgs
respectively.
If you were not already using v2 flags, follow the official Remix migration guide before upgrading to v2.
- Renaming types prefixed with
-
Update to Remix v2. Remix is now a peer dependency and its version is no longer pinned. This means that you can upgrade to newer Remix 2.x versions without upgrading Hydrogen. (#1289) by @frandiox
-
The default caching strategy has been updated. The new default caching strategy provides a
max-age
value of 1 second, and astale-while-revalidate
value of 1 day. If you would keep the old caching values, update your queries to useCacheShort
: (#1336) by @benjaminsehlconst {product} = await storefront.query( `#graphql query Product($handle: String!) { product(handle: $handle) { id title } } `, { variables: {handle: params.productHandle}, + /** + * Override the default caching strategy with the old caching values + */ + cache: storefront.CacheShort(), }, );
-
The Storefront API types included are now generated using
@graphql-codegen/typescript@4
(changelog). This results in a breaking change if you were importingScalars
directly from@shopify/hydrogen-react
or@shopify/hydrogen
: (#1108) by @frandioximport type {Scalars} from '@shopify/hydrogen/storefront-api-types'; type Props = { - id: Scalars['ID']; // This was a string + id: Scalars['ID']['input']; // Need to access 'input' or 'output' to get the string };
-
Add a client to query the Customer Account API (#1430) by @blittle
-
Update Storefront API version to 2023-10 (#1431) by @wizardlyhel
-
Custom cart methods are now stable: (#1440) by @wizardlyhel
const cart = createCartHandler({ storefront, getCartId, setCartId: cartSetIdDefault(), - customMethods__unstable: { + customMethods: { addLines: async (lines, optionalParams) => { // ... }, }, });
-
Remove deprecated parameters and props (#1455 and #1435): (#1435) by @wizardlyhel
createStorefrontClient
parametersbuyerIp
andrequestGroupId
<Image>
propsloaderOptions
andwidths
-
Add query explorer plugin to GraphiQL. Start your dev server and load
http://localhost:3000/graphiql
to use GraphiQL. (#1470) by @frandiox -
Updated dependencies [
0ae7cbe2
,ad45656c
]:- @shopify/[email protected]
-
Fix template dist package due to CI error (#1451) by @wizardlyhel
-
Updated dependencies [
3eb376fe
]:- @shopify/[email protected]
-
Move
react
to peer dependencies. It had been added as a direct dependency by mistake in a previous version. (#1439) by @frandiox -
Integrate the debug-network tooling with the new
--worker-unstable
runtime CLI flag. (#1387) by @frandiox -
Calls to
withCache
can now be shown in the/debug-network
tool when using the Worker runtime. For this to work, use the newrequest
parameter increateWithCache
: (#1438) by @frandioxexport default { fetch(request, env, executionContext) { // ... const withCache = createWithCache({ cache, waitUntil, + request, }); // ... }, }
-
Updated dependencies [
d30e2651
,1b45311d
,2627faa7
]:- @shopify/[email protected]
-
Allow generic inference in standalone usage of WithCache type - Contributed by @chinanderm (#1363) by @wizardlyhel
-
Cart Optimistic UI helpers (#1366) by @wizardlyhel
-
Fix storefront sub request cache key (#1375) by @wizardlyhel
-
Fix the Pagination component to use forwardRefs for the NextLink and PreviousLink render props (#1362) by @blittle
-
The
error.cause
property throw from the Storefront client is now stringified. (#1184) by @frandiox -
Fix Hydrogen's Storefront API client to not throw unhandled promise exceptions. This is because Remix is guaranteed to handle exceptions from the loader and fixing it prevents Hydrogen from crashing when deployed to some runtimes on unhandled promise exceptions. (#1318) by @blittle
-
Relax prop validation on the
getSelectedProductOptions
andgetSelectedProductOptions
utilities to look for member props instead of checking withinstanceof
. (#1327) by @blittle
-
Supress the hydration warning in the new
<Script>
component whennonce
values differ between the server and client, which is expected. (#1312) by @frandiox -
(Unstable) server-side network request debug virtual route (#1284) by @wizardlyhel
-
Update your
server.ts
so that it also passes in thewaitUntil
andenv
.const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, + getLoadContext: () => ({session, storefront, env, waitUntil}), });
If you are using typescript, make sure to update
remix.env.d.ts
declare module '@shopify/remix-oxygen' { export interface AppLoadContext { + env: Env; cart: HydrogenCart; storefront: Storefront; session: HydrogenSession; + waitUntil: ExecutionContext['waitUntil']; } }
-
Run
npm run dev
and you should see terminal log information about a new virtual route that you can view server-side network requests at http://localhost:3000/debug-network -
Open http://localhost:3000/debug-network in a tab and your app another tab. When you navigate around your app, you should see server network requests being logged in the debug-network tab
-
- Updated dependencies [
345f06a2
]:- @shopify/[email protected]
-
Fix the Pagination component to reset internal state when the URL changes (not including Pagination params). (#1291) by @blittle
We also now validate the connection prop to include a
pageInfo
object with the following properties:hasNextPage
hasPreviousPage
endCursor
startCursor
Previously our templates had a bug where
startCursor
was not included. Upgrading means the app will error until you update your query to include it:query CollectionDetails { collection(handle: $handle) { ... pageInfo { hasPreviousPage hasNextPage hasNextPage endCursor + startCursor } } }
-
Fix hydration errors and stale data within the Pagination component (#1283) by @blittle
-
Add custom product paths to the
VariantSelector
component: (#1271) by @blittle<VariantSelector handle="snowboard" productPath="shop" options={options}> {/* ... */} </VariantSelector>
-
Add functionality for creating a Content Security Policy. See the guide on Content Security Policies for more details. (#1235) by @blittle
-
Updated dependencies [
06516ee9
,423acee2
]:- @shopify/[email protected]
-
Exported the type
CookieOptions
fromcartSetIdDefault
(#1153) by @remcolakens -
Updated dependencies [
e9e1736a
,1a0e858d
]:- @shopify/[email protected]
-
Surface storefront api response errors (#1205) by @wizardlyhel
-
Updated dependencies [
d80c4ada
]:- @shopify/[email protected]
⭐️ Check out our blog post with all the latest updates on Hydrogen, and what’s coming on the roadmap.
The latest version of Hydrogen comes with new and updated components and utilities that can help you speed up your build:
- An updated server-side Cart component with built-in abstractions to handle most common cart operations, including adding, updating, or deleting line items, applying discounts, and more.
- A drop-in
<Pagination/>
component to make it easier to handle large product collections. - A new
<VariantSelector/>
component that makes it faster to build progressively enhanced product forms. - Improved support for predictive search and local pickup options through Storefront API version 2023-07.
-
createWithCache
is now stable. All imports need to be updated: (#1151) by @blittle- import {createWithCache_unstable} from '@shopify/hydrogen'; + import {createWithCache} from '@shopify/hydrogen';
-
Pagination
andgetPaginationVariables
are now stable. (#1129) by @blittleAll imports to each should be updated:
- import {Pagiatinon__unstable, getPaginationVariables__unstable} from '@shopify/hydrogen'; + import {Pagiatinon, getPaginationVariables} from '@shopify/hydrogen';
-
Function and component for cart management: (#786) by @wizardlyhel
createCartHandler
- Creates an object instance that simplifies cart operations such as add/update/remove from cart.CartForm
- A form component that helps you sets up form inputs for cart handler.
Documentation:
- Updated how-to guides
createCartHandler
CartForm
-
Export useLoadScript (#1080) by @wizardlyhel
-
Throw error when
storeDomain
is not passed tocreateStorefrontClient
. (#1128) by @frandiox -
Improve warning and error format for known Hydrogen messages in development. (#1093) by @frandiox
-
Add an example using the new Customer Account API (#1126) by @blittle
-
Corrected the
$attributes
type inCART_ATTRIBUTES_UPDATE_MUTATION
to match the expected one (#1117) by @remcolakens -
Fix cache key by url encode the sub request keys (#1105) by @wizardlyhel
Add a <VariantSelector>
component to make building product forms easier. Also added the getSelectedProductOptions
helper function. See the guide on using the VariantSelector. (#1027) by @blittle
- Updated dependencies [
b8f41ad7
]:- @shopify/[email protected]
-
Update Remix to the latest version (
1.17.1
). (#852) by @frandioxWhen updating your app, remember to also update your Remix dependencies to
1.17.1
in yourpackage.json
file:-"@remix-run/react": "1.15.0", +"@remix-run/react": "1.17.1", -"@remix-run/dev": "1.15.0", -"@remix-run/eslint-config": "1.15.0", +"@remix-run/dev": "1.17.1", +"@remix-run/eslint-config": "1.17.1",
-
A default
https://
protocol is now added automatically tostoreDomain
if missing. (#985) by @frandiox -
Fix
flattenConnection()
's TypeScript types when working withedges.node
(#945) by @frehner -
Make
storefrontApiVersion
parameter optional. By default, it will use the current version of Hydrogen as the Storefront API version. (#984) by @frandiox -
Skip reading and writing cache in sub-requests when the strategy is CacheNone. (#964) by @frandiox
-
Fix
<ModelViewer>
to properly set className (#966) by @blittle -
Add a
/admin
route that redirects to the Shopify admin. This redirect can be disabled by passingnoAdminRedirect: true
tostorefrontRedirect
: (#989) by @blittlestorefrontRedirect({ redirect, response, storefront, noAdminRedirect: true, });
-
Updated dependencies [
7b4afea2
,32515232
,7d6a1a7c
,442f602a
,b9ab8eb7
,93a7c3c6
]:- @shopify/[email protected]
-
Updated dependencies [
7aaa4e86
]:- @shopify/[email protected]
-
Add support for generated types from the new unstable codegen feature in the CLI. (#707) by @frandiox
-
Add a
<Pagination__unstable>
component andgetPaginationVariables__unstable
helper to make rendering large lists from the Storefront API easy. This is an initial unstable release and we expect to finalize the API by the 2023-07 release. See the<Pagination>
component documentation. (#755) by @cartogram -
Updated dependencies [
2e1e4590
]:- @shopify/[email protected]
-
Adds
parseGid()
which is a helper function that takes in a Shopify GID and returns theresource
andid
from it. For example: (#845) by @frehnerimport {parseGid} from '@shopify/hydrogen-react'; const {id, resource} = parseGid('gid://shopify/Order/123'); console.log(id); // 123 console.log(resource); // Order
-
Avoid warning about missing
buyerIp
when using private access tokens in development. (#836) by @frandiox -
Updated dependencies [
0a009a3b
]:- @shopify/[email protected]
-
Releases
2023-04
(#754) by @lordofthecactus -
Updates Hydrogen to Storefront 2023-04 API release.
-
Updates types from
CartLineConnection
toBaseCartLineConnection
. -
Deprecates
CartLinePrice
from@shopify/hydrogen-react
useMoney
instead:- import {CartLinePrice} from '@shopify/hydrogen-react'; + import {Money} from '@shopify/hydrogen-react';
- <CartLinePrice line={line} /> + <Money data={line.priceV2} />
-
Adds a new
Image
component, replacing the existing one. While your existing implementation won't break, propswidths
andloaderOptions
are now deprecated disregarded, with a newaspectRatio
prop added. (#787) by @benjaminsehlThe new
Image
component is responsive by default, and requires less configuration to ensure the right image size is being rendered on all screen sizes.Before
<Image data={image} widths={[400, 800, 1200]} width="100px" sizes="90vw" loaderOptions={{ scale: 2, crop: 'left', }} />
After
<Image data={image} sizes="90vw" crop="left" aspectRatio="3/2" />
Note that
widths
andloaderOptions
have now been deprecated, declaringwidth
is no longer necessary, and we’ve added anaspectRatio
prop:widths
is now calculated automatically based on a newsrcSetOptions
prop (see below for details).loaderOptions
has been removed in favour of declaringcrop
andsrc
as props.width
andheight
should only be set as props if rendering a fixed image size, withwidth
otherwise defaulting to100%
, and the loader calculating each dynamically.aspectRatio
is calculated automatically usingdata.width
anddata.height
(if available) — but if you want to present an image with an aspect ratio other than what was uploaded, you can set using the formatInt/Int
(e.g.3/2
, see MDN docs for more info, note that you must use the fraction style of declaring aspect ratio, decimals are not supported); if you've set anaspectRatio
, we will default the crop to becrop: center
(in the example above we've specified this to useleft
instead).
<Image data={data} />
This would use all default props, which if exhaustively declared would be the same as typing:
<Image data={data} crop="center" decoding="async" loading="lazy" width="100%" sizes="100vw" srcSetOptions={{ interval: 15, startingWidth: 200, incrementSize: 200, placeholderWidth: 100, }} />
An alternative way to write this without using
data
would be to use thesrc
,alt
, andaspectRatio
props. For example:<Image src={data.url} alt={data.altText} aspectRatio={`${data.width}/${data.height}`} />
Assuming
data
had the following shape:{ "url": "https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg", "altText": "alt text", "width": "4000", "height": "4000" }
All three above examples would result in the following HTML:
<img srcset="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=300&height=300&crop=center 300w, … *13 additional sizes* … https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=3000&height=3000&crop=center 3000w" src="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=100&height=100&crop=center" alt="alt text" sizes="100vw" loading="lazy" decoding="async" width="100px" height="100px" style="aspect-ratio: 4000 / 4000;" />
When using images that are meant to be a fixed size, like showing a preview image of a product in the cart, instead of using
aspectRatio
, you'll instead declarewidth
andheight
manually with fixed values. For example:<Image data={data} width={80} height={80} />
Instead of generating 15 images for a broad range of screen sizes,
Image
will instead only generate 3, for various screen pixel densities (1x, 2x, and 3x). The above example would result in the following HTML:<img srcset=" https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=80&height=80&crop=center 1x, https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=160&height=160&crop=center 2x, https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=240&height=240&crop=center 3x " src="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=80&height=80" alt="alt text" loading="lazy" width="80px" height="80px" style="aspect-ratio: 80 / 80;" />
If you don't want to have a fixed aspect ratio, and instead respect whatever is returned from your query, the following syntax can also be used:
<Image data={data} width="5rem" />
Which would result in the same HTML as above, however the generated URLs inside the
src
andsrcset
attributes would not haveheight
orcrop
parameters appended to them, and the generatedaspect-ratio
instyle
would be4000 / 4000
(if using the samedata
values as our original example).If your image isn't coming from the Storefront API, but you still want to take advantage of the
Image
component, you can pass a customloader
prop, provided the CDN you're working with supports URL-based transformations.The
loader
is a function which expects aparams
argument of the following type:type LoaderParams = { /** The base URL of the image */ src?: ImageType['url']; /** The URL param that controls width */ width?: number; /** The URL param that controls height */ height?: number; /** The URL param that controls the cropping region */ crop?: Crop; };
Here is an example of using
Image
with a custom loader function:const customLoader = ({src, width, height, crop}) => { return `${src}?w=${width}&h=${height}&gravity=${crop}`; }; export default function CustomImage(props) { <Image loader={customLoader} {...props} />; } // In Use: <CustomImage data={customCDNImageData} />;
If your CDN happens to support the same semantics as Shopify (URL params of
width
,height
, andcrop
) — the default loader will work a non-Shopifysrc
attribute.An example output might look like:
https://mycdn.com/image.jpeg?width=100&height=100&crop=center
-
Added the
srcSetOptions
prop used to create the image URLs used insrcset
. It’s an object with the following keys and defaults:srcSetOptions = { intervals: 15, // The number of sizes to generate startingWidth: 200, // The smalles image size incrementSize: 200, // The increment by to increase for each size, in pixesl placeholderWidth: 100, // The size used for placeholder fallback images };
-
Added an export for
IMAGE_FRAGMENT
, which can be imported from Hydrogen and used in any Storefront API query, which will fetch the required fields needed by the component. -
Added an export for
shopifyLoader
for using Storefront API responses in conjunction with alternative frameworks that already have their ownImage
component, like Next.js
- Updated dependencies [
82b6af7
,361879e
]:- @shopify/[email protected]
-
Bump internal Remix dependencies to 1.15.0. (#728) by @wizardlyhel
Recommendations to follow:
- Upgrade all the Remix packages in your app to 1.15.0.
- Enable Remix v2 future flags at your earliest convenience following the official guide.
-
Add an experimental
createWithCache_unstable
utility, which creates a function similar touseQuery
from Hydrogen v1. Use this utility to query third-party APIs and apply custom cache options. (#600) by @frandioxTo setup the utility, update your
server.ts
:import { createStorefrontClient, createWithCache_unstable, CacheLong, } from '@shopify/hydrogen'; // ... const cache = await caches.open('hydrogen'); const withCache = createWithCache_unstable({cache, waitUntil}); // Create custom utilities to query third-party APIs: const fetchMyCMS = (query) => { // Prefix the cache key and make it unique based on arguments. return withCache(['my-cms', query], CacheLong(), () => { const cmsData = await (await fetch('my-cms.com/api', { method: 'POST', body: query })).json(); const nextPage = (await fetch('my-cms.com/api', { method: 'POST', body: cmsData1.nextPageQuery, })).json(); return {...cmsData, nextPage} }); }; const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, getLoadContext: () => ({ session, waitUntil, storefront, env, fetchMyCMS, }), });
Note: The utility is unstable and subject to change before stabalizing in the 2023.04 release.
-
Updated dependencies [
85ae63a
,5e26503
]:- @shopify/[email protected]
-
Add new
loader
API for setting seo tags within route module (#591) by @cartogram -
ShopPayButton
component now can receive astoreDomain
. The component now does not requireShopifyProvider
. (#645) by @lordofthecactus -
Added
robots
option to SEO config that allows users granular control over the robots meta tag. This can be set on both a global and per-page basis using the handle.seo property. (#572) by @cartogramExample:
export handle = { seo: { robots: { noIndex: false, noFollow: false, } } }
-
Fix active cart session event in Live View (#614) by @wizardlyhel
Introducing
getStorefrontHeaders
that collects the required Shopify headers for making a Storefront API call.- Make cart constants available as exports from
@shopify/hydrogen-react
- Deprecating
buyerIp
andrequestGroupId
props fromcreateStorefrontClient
from@shopify/hydrogen
- Deprecating
getBuyerIp
function from@shopify/remix-oxygen
+ import {getStorefrontHeaders} from '@shopify/remix-oxygen'; import {createStorefrontClient, storefrontRedirect} from '@shopify/hydrogen'; export default { async fetch( request: Request, env: Env, executionContext: ExecutionContext, ): Promise<Response> { const {storefront} = createStorefrontClient({ cache, waitUntil, - buyerIp: getBuyerIp(request), i18n: {language: 'EN', country: 'US'}, publicStorefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN, privateStorefrontToken: env.PRIVATE_STOREFRONT_API_TOKEN, storeDomain: `https://${env.PUBLIC_STORE_DOMAIN}`, storefrontApiVersion: env.PUBLIC_STOREFRONT_API_VERSION || '2023-01', storefrontId: env.PUBLIC_STOREFRONT_ID, - requestGroupId: request.headers.get('request-id'), + storefrontHeaders: getStorefrontHeaders(request), });
- Make cart constants available as exports from
-
Updated dependencies [
c78f441
,7fca5d5
]:- @shopify/[email protected]
- Fix template imports to only reference
@shopify/hydrogen
, not@shopify/hydrogen-react
(#523) by @blittle
-
Send Hydrogen version in Storefront API requests. (#471) by @frandiox
-
Fix default Storefront type in LoaderArgs. (#496) by @frandiox
- Initial release