- Fix issues with cart analytics described in Shopify/hydrogen#2382 (#2388) by @lordofthecactus
- Fix an issue where non-string server prop values get in-properly parsed. Resolves Shopify/hydrogen#2365 (#2376) by @blittle
- Use correct defaultCartFragment in CartProvider.client.tsx This resolves an error sending Add To Cart events to Shopify Analytics (#2332) by @mperreux
- Updates links to https://shopify.dev/custom-storefronts/hydrogen in favor of a new information architecture. We've split out conceptual material from procedural material and organized the content by area of work. (#2326) by @rennyG
-
Add a helper method to get headers to proxy the online store. These headers are necessary to prevent the online store from throttling proxied requests: (#2300) by @blittle
import {getOnlineStorefrontHeaders} from '@shopify/hydrogen'; async function handleEvent(event) { const response = fetch(`https://hydrogen.shop/products/hydrogen`, { headers: getOnlineStorefrontHeaders(event.request), }); return response; }
-
Remove automatic origin support from
fetchSync
on the server. (#2276) by @jplhomerImportant: Please test that your Hydrogen app is using
fetchSync
correctly before shipping this version of Hydrogen to production.Developers should never be making
fetch
requests on the server against their own Hydrogen app. This is because some production runtimes prohibit invokingfetch
requests to servers in the same region. Other runtimes will fail to resolve DNS when invoked from within the same process.This change makes it required to pass a fully-qualified URL (including origin) to
fetchSync
when it's being used on the server:// MyComponent.server.jsx // ❌ You should not use this pattern, and it will now fail: fetchSync('/api/path').json();
Instead, you should query the data directly, or extract the data query to a function and call it inside your server component:
// MyComponent.server.jsx import {sharedQuery} from './shared-location.server'; // ✅ Do this instead: useQuery('shared-query', sharedQuery);
This is not considered a breaking change because the intention of the server-side
fetchSync
API was never to enable calling a Hydrogen app from itself, but rather to call third-party APIs from the server.
- Fix issue with preload cache which would cause problems when scaled to large amounts of traffic in production. (#2316) by @jplhomer
- Fix RSC responses in some situations where the pathname contains double quotes. (#2320) by @frandiox
We’re excited to announce an experimental version of Hydrogen UI React is now available! We’ve decoupled key components, hooks, and utilities from the Hydrogen framework and moved them into a new package called @shopify/hydrogen-react
.
You can now bring Hydrogen functionality to your React framework of choice!
The components, hooks, and utilities will continue to be supported in @shopify/hydrogen
until Hydrogen UI React is made generally available, but all new features will only go into @shopify/hydrogen-react
going forward.
Check out the documentation and the repository to learn more!
- Fix RSC response caching (#2260) by @wizardlyhel
- Allow
basePath
to be passed toRoute
to support path-based internationalization inside custom route (#2252) by @jplhomer
-
Add Shopify analytics instrumentation for customer events. (#2238) by @wizardlyhel
See the updated doc on
<ShopifyAnalytics />
.
-
Updates
CartProvider
to use the newCartProviderV2
. No code changes are necessary. Docs for CartProvider can be found here. (#2182) by @lordofthecactusAlso adds the following
onComplete
props callbacks:Name Type Description onCreateComplete? () => void
Invoked when the process to create a cart completes successfully onLineAddComplete? () => void
Invoked when the process to add a line item to the cart completes successfully onLineRemoveComplete? () => void
Invoked when the process to remove a line item to the cart completes successfully onLineUpdateComplete? () => void
Invoked when the process to update a line item in the cart completes successfully onNoteUpdateComplete? () => void
Invoked when the process to add or update a note in the cart completes successfully onBuyerIdentityUpdateComplete? () => void
Invoked when the process to update the buyer identity completes successfully onAttributesUpdateComplete? () => void
Invoked when the process to update the cart attributes completes successfully onDiscountCodesUpdateComplete? () => void
Invoked when the process to update the cart discount codes completes successfully
- Added missing dependancy for faker to hydrogen package (#2234) by @Drew-Garratt
- Add more error catches on shopify analytics (#2256) by @wizardlyhel
-
Special thank you to @kcarra for adding new mocked Providers for making testing easier! (#2224) by @blittle
- Add
ServerRequestProvider
mock for testing server components:
import useServerHook from './useServerHook.server'; // Server hook to test import {test, vi} from 'vitest'; import {renderHook} from '@testing-library/react-hooks'; import {ShopifyProvider} from '@shopify/hydrogen'; import {MockedServerRequestProvider} from '@shopify/hydrogen/testing'; describe('useServerHook', () => { test('mocked ServerRequest Context', () => { const wrapper = ({children}: {children: React.ReactElement}) => ( <MockedServerRequestProvider> <ShopifyProvider shopifyConfig={mockShopifyConfig}> {children} </ShopifyProvider> </MockedServerRequestProvider> ); const {result} = renderHook(() => useServerHook(), {wrapper}); expect(result.current).toEqual({status: 'active'}); }); });
- Add
ShopifyTestProviders
mock for easier testing client components and using client components in other contexts, like Storybook:
import {ComponentMeta, ComponentStory} from '@storybook/react'; import React from 'react'; import BoxCardUI from './BoxCard.ui'; import {ShopifyTestProviders} from '@shopify/hydrogen/testing'; export default { title: 'Components/BoxCard', component: BoxCardUI, decorators: [], } as ComponentMeta<typeof BoxCardUI>; const Template: ComponentStory<typeof BoxCardUI> = (args) => { return ( <ShopifyTestProviders> <BoxCardUI {...args} /> // This component imports import{' '} {(Image, Link, Money)} from '@shopify/hydrogen' </ShopifyTestProviders> ); }; export const BoxCard = Template.bind({}); BoxCard.args = mockShopifyProduct;
- Add
-
Updated the Storefront API version of Hydrogen to the
2022-10
release. (#2208) by @frehnerThis is a backwards-compatible change; if you are still on the
2022-07
version, you may stay on that version without any issues. However, it is still recommended that you upgrade to2022-10
as soon as possible.For more information about the Storefront API, refer to:
- The versioning documentation
- The
2022-10
release notes. Take note that Hydrogen never used theMoney
fields internally, so the breaking change listed there does not affect Hydrogen.
-
Experimental version of a new cart provider is ready for beta testing. (#2219) by @lordofthecactus
CartProviderV2
fixes race conditions with our current cart provider. After beta,CartProviderV2
will becomeCartProvider
requiring no code changes.To try this new cart provider:
import {CartProviderV2} from '@shopify/hydrogen/experimental';
- Prevent unhandled errors when
location.hash
is not a valid element selector for scrolling. (#2209) by @potench
- Changing the content of a CSS Module should now trigger HMR instead of a full page refresh when using
global
CSS mode. (#2175) by @frandiox
- Fix imports from
@shopify/hydrogen/experimental
at build time. Previously, importing from this path would end up in unresolved client components. (#2198) by @frandiox
- Critical fix for the CartProvider to remove an error when Cookies are disabled by the browser. (#2190) by @blittle
- Fix storefrontId from required to optional param (#2162) by @wizardlyhel
- We changed the default logging behavior to include the overall request outcome, either
ok
or anerror
. This is necessary because a streamed request might start with a 200 HTTP response code, and during the process of stream rendering an error is encountered. (#2161) by @blittle
- Expose CachingStrategy type (#2159) by @wizardlyhel
- Important bug fix for backwards compatibility with old environment variables. Followup from the change in 1.4.0 (#2151) by @blittle
- Add the
useDelay
hook for artificial delays when rendering server components. This is useful to debug timing issues or building suspense boundary fallback UI. See theuseDelay
documentation. (#2109) by @blittle
-
We've exposed the private server-to-server Storefront API token in the Hydrogen config file. This private token is required when deploying to production, otherwise the requests to the storefront API will be rate-limited. This change will make it easier to configure Hydrogen when deploying to non-Oxygen environments. We'll also display a warning in production mode if this token is not defined. (#1998) by @blittle
We've also added the
storefrontId
property to the config. This enables Hydrogen data to display properly in the Shopify admin analytics dashboard.Lastly, we've updated all Oxygen environment variables to a more consistent naming convention. The previous variables are still available, but are deprecated, and will be removed in the future. You’ll see a warning in your console if you use the old environment variables. You can update your variable references using this table:
Old Oxygen variable New Oxygen variable SHOPIFY_STORE_DOMAIN PUBLIC_STORE_DOMAIN SHOPIFY_STOREFRONT_API_PUBLIC_TOKEN PUBLIC_STOREFRONT_API_TOKEN SHOPIFY_STOREFRONT_API_SECRET_TOKEN PRIVATE_STOREFRONT_API_TOKEN SHOPIFY_STOREFRONT_ID PUBLIC_STOREFRONT_ID
- Fixed dev-mode Hydrogen builds to properly show
PUBLIC_
prefixed environment variables (#2142) by @blittle
- An issue with previewing hydrogen in production has been fixed. Make sure that you upgrade both
@shopify/cli
and@shopify/cli-hydrogen
to 3.12.0. (#2144) by @blittle
-
Whenever using
fetchSync
, make sure to handle the error state. Though we've made changes to the error thrown by the JSON parser to also tell you that the request was unsuccessful: (#2070) by @blittlefunction MyComponent() { const response = fetchSync('/api'); // Make sure the error state is handled! if (!response.ok) { console.error( `Unable to load ${response.url} returned ${response.status}`, ); return <div>Error. Please try again</div>; } // Check `response.ok` before parsing the response const json = response.json(); return ...
- Update undici to the latest (#2015) by @dependabot
- Added experimental support for Vite 3. By default, Hydrogen will still use Vite 2. However, it is possible to upgrade apps to Vite 3 by changing
devDependencies
in the apppackage.json
. Beware that this is experimental and it might break. (#1992) by @frandiox
-
Improvements and fixes to hydrogen logging: (#2084) by @blittle
- API Routes are now passed a reference to the logger bound to the current request:
export async function api(request, {log}) { log.warn("Here's a warning!"); return new Request('Hello World'); }
- If you define a custom logging implementation within your Hydrogen config, we'll now warn you when your logging implementation itself errors.
- When a route is rendering, if Hydrogen has already started streaming, it is invalid to call
response.doNotStream()
. Disabling streaming should always happen before any async operation in your route server component. This change fixes Hydrogen to warn if you try to disable streaming after the stream has already begun. (#2081) by @frandiox
-
<ExternalVideo/>
now has a default prop ofloading="lazy"
to improve performance of the rendered<iframe>
. (#2044) by @frehnerIf you're using
<ExternalVideo/>
above the fold, then we recommend setting this prop toeager
.
-
Improve error handling: (#2049) by @blittle
- Improve how errors are default presented in the logs.
- Make sure that when useShopQuery fails, that an Error object is propagated.
If you have implemented your own logging handler, it is recommended that you only print strings, as printing objects (including Error objects) will result in unhelpful logs in many runtimes (Oxygen included):
// Example custom logging for errors export default defineConfig({ logger: { error: (context, error) => { const url = context ? ` ${context.url}` : ''; if (error instanceof Error) { // Do NOT directly just print the error, instead // print the error.messag or error.stack console.error(`Error:${url}\n${error.stack}`); } else { console.error(`Error:${url} ${error}`); } }, }, });
-
Add the experimental
useFlashSession
hook. This hook reads and clears a session value. It is useful for request validation within the experimental<Form>
component: (#1878) by @blittleimport {Form, useFlashSession} from '@shopify/hydrogen/experimental'; export default function Login() { const loginError = useFlashSession('loginError'); return ( <Form action="/login"> {loginError ? <div>Invalid user!</div> : null} <input type="text" name="username" /> <input type="password" name="password" /> <button type="submit">Login</button> </Form> ); } export async function api(request, {session}) { const data = await request.formData(); const username = data.get('username'); const password = data.get('password'); const userId = await getUser(username, password); if (!userId) { await session.set('loginError', 'INVALID_USER'); return new Request('/login'); } else { await session.set('userId', userId); return new Request('/account'); } }
Note,
useFlashSession
is experimental, and subject to change at any time.
- Pass root to all Vite instances to support building projects from different directories. (#1987) by @frandiox
-
Added a new option
assetHashVersion
to the Hydrogen plugin invite.config.js
. This option can be used to manually change the assets file hash. (#2000) by @frandiox// vite.config.js export default { plugins: [hydrogen({assetHashVersion: 'v2'})], };
<Image/>
now sets the attributedecoding='async'
by default, to potentially improve performance. (#1969) by @sanjaiyan-dev
- Fix stale while revalidate when custom cache value is supplied (#1967) by @wizardlyhel
-
The Typescript types for the
<Image/>
component are now available to import directly from Hydrogen. (#1913) by @frehnerimport { type ShopifyImageProps, type ExternalImageProps, } from '@shopify/hydrogen';
- Change how the RSC plugin communicates with other plugins to support
vanilla-extract
. (#1944) by @frandiox
- Adds
merchandise.product.id
to cart line items query (#1988) by @juanpprieto
- Adds
prevCart
to cart event payloads (#1982) by @juanpprieto
- Add a new experimental Forms and mutations feature. Read more https://shopify.dev/custom-storefronts/hydrogen/forms (#1552) by @blittle
CartLineQuantityAdjustButton
,BuyNowButton
, andAddToCartButton
now have anas
property. The types for these components have also been improved. (#1827) by @blittle
-
Added a new experimental CSS approach to support importing styles directly in React Server Components. This feature must be enabled manually. (#1843) by @frandiox
Until now, we had experimental support for CSS Modules with some minor restrictions and drawbacks:
- Only server components that were the default export had access to the CSS Module automatically (i.e. it required extra work for named exports).
- CSS Module was duplicated when used in multiple components.
- RSC responses had all the CSS inlined, making them much larger.
The new CSS approach adds full support for CSS Modules without the previous restrictions and drawbacks.
Aside from that, it also adds support for pure CSS and enables a way to integrate with tools that provide CSS-in-JS at build time. All the CSS imported in both client and server components will be extracted in a CSS file at build time and downloaded with a
<link rel="stylesheet">
tag. During development, styles will be inlined in the DOM to better support HMR.To activate this new experimental feature, pass the
experimental.css: 'global'
option to Hydrogen's Vite plugin:// vite.config.js export default { plugins: [hydrogen({experimental: {css: 'global'}})], };
Examples:
// App.server.jsx using pure CSS with global classes import './my-red-style.css'; function App() { return <div className="red">...</div>; }
// App.server.jsx using CSS Modules with scoped classes import {red} from './my-red.module.css'; function App() { return <div className={red}>...</div>; }
loadScript
anduseLoadScript
can now inject in the (#1870) by @juanpprieto
-
Add support for custom 500 error pages. If an unexpected error occurs while rendering a route, Hydrogen will respond with a 500 HTTP error and render a default error page. Define a custom error page with the
serverErrorPage
configuration property: (#1867) by @blittleimport {defineConfig} from '@shopify/hydrogen/config'; export default defineConfig({ ... serverErrorPage: '/src/Error.jsx', });
The
serverErrorPage
property defaults to/src/Error.{jsx,tsx}
. The custom error page is passed anError
property:export default function Error({error}) { return ( <div> <h1>An unknown error occured!</h1> <h2>{error.message}</h2> <h3>{error.stack}</h3> </div> ); }
- Log detailed error message for Storefront API root errors (#1822) by @wizardlyhel
-
locale
calculation logic and docs have been updated to support Shopify languages with extended language subtags. (#1836) by @lordofthecactusThe following is how we calculate
locale
: If the Shopifylanguage
includes a region, then this value is used to calculate thelocale
andcountryCode
is disregarded. For example, iflanguage
isPT_BR
(Brazilian Portuguese), thenlocale
is calculated asPT-BR
. If the Shopifylanguage
doesn't include a region, then this value is merged with thecountryCode
to calculate thelocale
. For example, iflanguage
isEN
(English) andcountryCode
isUS
(United States), thenlocale
is calculated asEN-US
.
- Added optional sellingPlanId prop to AddToCartButton.client.tsx (#1821) by @ChrisKG32
- Fix: Hydrogen no longer caches error responses. Any 400 or 500 level response will not have a cache control-header, nor will Hydrogen cache it internally. (#1873) by @blittle
- Thanks to @mrkldshv for help in migrating tests from Jest to Vitest.
- Thanks to @davidhousedev for constant feedback and discussions.
If your Store is based on the "Demo Store" tempate, and you are using the test:ci
NPM script, then you need to replace the contents of your /tests/utils.js
or /tests/utils.ts
file with the following:
- For Typescript projects, replace
/tests/utils.ts
with the content found here - For Javascript projects, replace
/tests/utils.js
with the content found here
- Replace graphiql with graphql/graphql-playground in local development at '/graphql` route. (#1710) by @cartogram
-
Expose utilities for integrating Hydrogen with 3rd party platforms in
@shopify/hydrogen/platforms
. These utilities can resolve the project build path automatically and also find the client build assets. (#1772) by @frandioximport { handleRequest, // Instead of './src/App.server' indexTemplate, // Instead of './dist/client/index.html?raw' isAsset, // Access a list of files in './dist/client/**/*' } from '@shopify/hydrogen/platforms'; // Platform entry handler export default function(request) { if (isAsset(new URL(request.url).pathname)) { return platformAssetHandler(request); } return handleRequest(request, {indexTemplate}); }
Note that user apps don't need to be changed.
- Fix server props to properly reset on page navigation. Fixes Shopify/hydrogen#1817 (#1830) by @blittle
-
Serve assets in
public
directory from the same origin when deploying to Oxygen. (#1815) by @frandioxNormally, public assets are served from a CDN domain that is different from the storefront URL. This creates issues in some situations where the assets need to be served from the same origin, such as when using service workers in PWA or tools like Partytown. This change adds a proxy so that the browser can download assets from the same origin.
Note that, for performance reasons, it is still recommended placing assets in
/src/assets
instead of/public
whenever possible.
-
The payload returned by
fetchSync
was supposed to mimicreact-fetch
but it wrongly moved the Response data to a sub-propertyresponse
. This has been fixed to have the Response at the top level. Also, cached responses are now correctly serialized and retrieved to avoid issues on cache hit. (#1760) by @frandioxconst response = fetchSync('...'); -response.response.headers.get('...'); +response.headers.get('...'); const jsonData = response.json();
Note that the sub-property
response
is still available but marked as deprecated.
- Improve error messaging when there is an error in the Storefront API's GraphQL response. (#1837) by @frehner
null
shopId fix on thePerformanceMetrics
component (#1722) by @wizardlyhel
- Make sure full page caching only caches on GET request (#1839) by @wizardlyhel
- Fix HMR in client components. It should now update only the modified client component in the browser instead of refreshing the entire page. (#1818) by @frandiox
<Image/>
component has improved handling forwidth
as a string. It also corrects an issue with thescale
prop and its usage with the Shopify CDN. The generatedsrcset
is also updated and improved. (#1723) by @frehner
-
We've decided to deprecate the
<LocalizationProvider>
, and instead put all its functionality into<ShopifyProvider>
. The justification is that both providers are required by many components and hooks, and we think it's easier to have a single required<ShopifyProvider>
instead of two. The same props available to the<LocalizationProvider>
are now available on the<ShopifyProvider>
. (#1735) by @blittle// App.server.tsx function App({routes, request}: HydrogenRouteProps) { ... return ( <Suspense fallback={<HeaderFallback isHome={isHome} />}> + <ShopifyProvider countryCode={countryCode as CountryCode}> - <ShopifyProvider> - <LocalizationProvider countryCode={countryCode as CountryCode}> <CartProvider countryCode={countryCode as CountryCode}> ... </CartProvider> <PerformanceMetrics /> {import.meta.env.DEV && <PerformanceMetricsDebug />} <ShopifyAnalytics /> - </LocalizationProvider> </ShopifyProvider> </Suspense> ); }
Note: this is not a breaking change.
<LocalizationProvider>
will still be available, but all documentation will now point to<ShopifyProvider>
.
- #1716
4e6356e6
Thanks @wizardlyhel! - Fix add to cart Shopify session tracking
- #1700
9b6c564e
Thanks @jplhomer! - Hydrogen is now out of developer preview. Thank you for all of your feedback and contributions the past eight months!
-
#1697
85aab092
Thanks @blittle! - RemovedefaultLocale
from the Hydrogen Config and instead adddefaultCountryCode
anddefaultLanguageCode
. Both of which are also now available by theuseShop()
hook:export default defineConfig({ shopify: { - defaultLocale: 'EN-US', + defaultCountryCode: 'US', + defaultLanguageCode: 'EN', storeDomain: 'hydrogen-preview.myshopify.com', storefrontToken: '3b580e70970c4528da70c98e097c2fa0', storefrontApiVersion: '2022-07', }, }
-
#1662
4262b319
Thanks @wizardlyhel! - Fix server analytics route- Fix ServerAnalyticsRoute so that it does complete all async work
- Move Performance and Shopify analytic reporting to client side
- Make sure
ShopifyAnalytics
make its own query for shop id and currency - Remove query for shop id and currency from
DefaultSeo
component - Make Performance and Shopify server analytics connector do nothing
Remove the following components from
hydrogen.config.js
PerformanceMetricsServerAnalyticsConnector
ShopifyServerAnalyticsConnector
- #1674
8068d3ce
Thanks @frandiox! - Throw error when<Link>
component is used outside of<Router>
component.
- #1680
acf5223f
Thanks @blittle! - Fix basepath to not apply to external URLs in the<Link
component. Also default the attributerel="noreferrer noopener
for external URLs.
-
#1615
20bfc438
Thanks @frehner! -<CartEstimatedCost/>
has been renamed to<CartCost/>
to match a recent update to the Storefront API, in whichcart.estimatedCost
is being deprecated in favor ofcart.cost
.Additionally,
cart.cost.compareAtAmount
was renamed tocart.cost.compareAtAmountPerQuantity
.
-
#1619
b0c13696
Thanks @blittle! - We have reworked how localization works in Hydrogen. By default theuseLocalization()
hook returns the default locale defined within your Hydrogen configuration file. The<LocalizationProvider>
component now takescountryCode
andlanguageCode
as optional props. Any props given to<LocalizationProvider>
will also be used by theuseLocalization
hook.Breaking Change
The
useCountry
hook has been removed. Instead use theuseLocalization
hook.- import {useCountry, gql} from '@shopify/hydrogen'; + import {useLocalization, gql} from '@shopify/hydrogen'; export function MyComponent() { - const [country] = useCountry(); + const {country} = useLocalization(); return ( /* Your JSX */ ); }
The
Link
component now respects thebasePath
property defined within it's parentFileRoutes
component. For example, given<FileRoutes basePath="/cn">
, a route within that renders<Link to="/products">
will actually produce an anchor tag prefixed with/cn
:<a href="/cn/products">
. You can override thebasePath
with abasePath
prop on theLink
component.
- #1646
1103fb57
Thanks @benjaminsehl! - Updates default SEO titleTemplate for the Homepage
- #1569
e5896a3e
Thanks @wizardlyhel! - Clean up full page cache work withwaitUntil
- #1613
c45ebd3c
Thanks @frehner! - The<ShopPayButton/>
and<CartShopPayButton/>
now take in awidth
prop to help customize how wide the<shop-pay-button>
custom element is, by using the newly added CSS custom property (variable)--shop-pay-button-width
.
-
#1651
a19be2b2
Thanks @blittle! - Fixes to the cart:- Fix bug when providing a lower-case country code to the
LocalizationProvider
- Make sure that the Cart always logs API errors
- Fix bug when providing a lower-case country code to the
- #1649
df0e01ff
Thanks @blittle! - Add ax-powered-by: Shopify-Hydrogen
header which can be disabled with the Hydrogen config property:poweredByHeader: false
- #1566
cfe7385e
Thanks @wizardlyhel! - Add storefont id to cart provider query
- #1551
3d20e92d
Thanks @jplhomer! - In-Memory caching is now enabled by default in production for Node.js runtimes.
-
#1604
f3827d31
Thanks @cartogram! - Adds newload-config
entry point that exposes aloadConfig()
function that will return the configuration object and the path to the found configuration file for a given Hydrogen project root.Example:
import {loadConfig} from '@shopify/hydrogen/load-config'; const {configuration, configurationPath} = await loadConfig({ root: 'path/to/hydrogen-app', });
- #1608
b834dfdc
Thanks @jplhomer! - Add type exports forHydrogenRouteProps
,HydrogenApiRoute
, andHydrogenApiRouteOptions
.
- #1603
e1bb5810
Thanks @frandiox! - Do not trigger prefetch whento
prop is missing in theLink
component.
-
#1570
36f26e54
Thanks @frehner! -<Image/>
now takes into account a specific order for determining the width and height.loaderOptions
's width/height- width/height bare props
data
's width/height
getShopifyImageDimensions()
was also updated to handle this logic.
- #1506
58d6ef55
Thanks @wizardlyhel! - Hydrogen now supports full-page caching out of the box. Previously, Hydrogen relied on the network edge to provide full-page caching for dynamic responses (HTML).
- #1346
01814369
Thanks @lordofthecactus! - AddonClick
andbuttonRef
props toAddToCartButton
,BuyNowButton
andCartLineQuantityAdjustButton
- #1523
4ef2e5b9
Thanks @blittle! - We've simplified the built-in Hydrogen caching strategies. Instead ofCacheSeconds
,CacheMinutes
,CacheHours
,CacheDays
,CacheMonths
, andNoStore
, there is no simplyCacheLong
,CacheShort
, andCacheNone
. Please remember that you can build your own caching strategies.
-
#1513
8d67b559
Thanks @frandiox! - Breaking change: We are starting to useexports
property inpackage.json
to list all the entry points in this package.This might incur breaking changes in some rare cases when importing private properties from Hydrogen
dist
files. Notice that we discourage doing so for anything that is not publicly documented but, if your app was relying on some private properties, then this might help:-import {xyz} from '@shopify/hydrogen/dist/esnext/<internal-path>'; +import {xyz} from '@shopify/hydrogen/<internal-path>';
Aside from that, it is recommended that TypeScript projects update the
tsconfig.json
file to usecompilerOptions.moduleResolution: "node16"
to make sure Hydrogen types are loaded in your editor. For JavaScript projects, create or edit<root>/jsconfig.json
file with the following information to improve typings:{ "compilerOptions": { "target": "es2020", "module": "esnext", "moduleResolution": "node16", "lib": ["dom", "dom.iterable", "scripthost", "es2020"], "jsx": "react", "types": ["vite/client"] }, "exclude": ["node_modules", "dist"], "include": ["**/*.js", "**/*.jsx"] }
-
#1528
72d21b87
Thanks @frehner! - Metafields have changed in Storefront API2022-07
. We updated our code to work with that update, which means that the following changes will only work if you're using2022-07
or newer.Metafields have changed how you access them in the Storefront API. See the release notes for more details. In order to support the new way of querying metafields, Hydrogen has made the following updates:
Previously, the
<Metafield/>
component expected you to useuseParseMetafields()
before passing a metafield to it.Now,
<Metafield/>
will useparseMetafield()
itself so that you don't have to. However, this does mean that if you useparseMetafield()
and then pass it to<Metafield/>
, it will likely break because it will try to parse your metafield's value a second time.Deprecated
useParsedMetafields()
in favor ofparseMetafield()
.parseMetafield()
takes in a single metafield and returns a new object, and importantly it can be used on both the client and the server.If you need to memoize the value on the client, then you can do so using
React.memo
:import {useMemo} from 'react'; import {parseMetafield} from '@shopify/hydrogen'x function MyComponent() { const parsedMetafield = useMemo(() => parseMetafield(metafield), [metafield]); }
-
#1517
68b8185e
Thanks @frandiox! - Breaking change: The utilities used inhydrogen.config.js
file are now exported from@shopiy/hydrogen/config
instead of@shopify/hydrogen
:-import {defineConfig} from '@shopify/hydrogen/config'; import { + defineConfig, CookieSessionStorage, PerformanceMetricsServerAnalyticsConnector, ShopifyServerAnalyticsConnector, -} from '@shopify/hydrogen'; +} from '@shopify/hydrogen/config' export default defineConfig({ shopify: {/* ... */}, session: CookieSessionStorage('__session', { path: '/', /* ... */ }), serverAnalyticsConnectors: [ PerformanceMetricsServerAnalyticsConnector, ShopifyServerAnalyticsConnector, ], });
- #1579
2f75247c
Thanks @frandiox! - Support renaming client component exports in intermediate/facade files.
- #1562
d38f6413
Thanks @wizardlyhel! - Add storefront id to useShopQuery calls when available
- #1593
ae35b70b
Thanks @juanpprieto! - Ensure the effect that updates thecart.buyerIdenity.countryCode
is run whencountyCode
prop changes
- #1548
923cb140
Thanks @cartogram! - Add new devTools interface for performance, settings and graphQL tracking
- #1375
217b5f23
Thanks @blittle! - Add a built-in healthcheck route available at/__health
. It responds with a 200 and no body. Also suppresses server logs for built-in routes like healthcheck and analytics.
-
#1497
3364225f
Thanks @blittle! - Improve waterfall detection- Show a summary in dev mode with instructions on getting details
- Only show the waterfall warning the second time the page is loaded
- Don't show the waterfall warning on preloaded queries
- #1571
accdc78a
Thanks @jplhomer! - Upgrade Hydrogen to React v18.2. To update your app, runyarn add @shopify/hydrogen@latest react@latest react-dom@latest
.
- #1578
f5290393
Thanks @frandiox! - Fix an issue where newly imported client components were not found in the browser.
-
#1556
06f3d174
Thanks @blittle! - Add support forRequest.formData()
within API Routes for Node 16-17. Example:export async function api(request) { const formData = await request.formData(); const username = formData.get('user'); const password = formData.get('pass'); ... }
- #1489
e2ee2d45
Thanks @blittle! - In an effort to be performant by default, the preloaded queries are turned on by default when caching is also enabled. By default, each query has caching enabled too, sopreload
will on universally by default.
-
#1237
356e75f3
Thanks @frehner! - Updated Cart queries in two ways, one of which requires you to be using Storefront API2022-07
:CartLine
now usesCartLineEstimatedCost
'stotalAmount
field for calculating the Line's total, instead of doing it manually.- Cart now uses
totalQuantity
for calculating how many items are in the cart, instead of doing this manually. Note that this feature is only available in Storefront API2022-07
and newer.
- #1484
990bfd8b
Thanks @wizardlyhel! - Fix ClientAnalytics losing subscriber object when passed as a param
- #1509
05081b01
Thanks @jplhomer! - Fix<BuyNowButton>
so it can be rendered without being nested in a<CartProvider>
- #1471
5b4e08df
Thanks @frandiox! - Added an experimental hookuseRequestContext
that provides server-only context for third party integrations.
- #1486
a31e007d
Thanks @frehner! - Fix<ProductOptionsProvider/>
'ssetSelectedOptions()
function to update theselectedVariant
as well
-
#1389
9a21108f
Thanks @blittle! - Breaking changeThe utility
isClient
has been renamed toisBrowser
. This is because the utility really checks if the running context is a browser, not if the context is a client component.All client components by default also run on the server when they are server rendered. If you don't want that to happen, use the
isBrowser()
hook. Remember that anything not server rendered will be unavailable for SEO bots.
-
#1431
6975bdb9
Thanks @jplhomer! - Addscroll
prop toLink
andnavigate
to allow the scroll restoration behavior to be disabled.By default, when a
<Link>
component is clicked, Hydrogen emulates default browser behavior and attempts to restore the scroll position previously used in the visitor's session. For new pages, this defaults to scrolling to the top of the page.However, if you are building a user interface that should fetch a new server components request and update the URL but not modify scroll position, then you can disable scroll restoration using the
scroll
prop:import {Link} from '@shopify/hydrogen'; export default function Index({request}) { const url = new URL(request.normalizedUrl); return ( <> <p>Current param is: {url.searchParams.get('param')}</p> <Link to="/?param=foo" scroll={false}> Update param to foo </Link> </> ); }
-
#1325
572c18d1
Thanks @wizardlyhel! - - Fix clientAnalytics not waiting for all server analytics data before sending page view event- Fix server analytics connector erroring out after more than 1 server analytics connectors are attached
- Shopify analytics components
The server analytics connector interface has updated to
export function request( requestUrl: string, requestHeader: Headers, data?: any, contentType?: string ): void { // Do something with the analytic event. }
Optional analytics components that allows you to send ecommerce related analytics to Shopify. Adding the Shopify analytics components will allow the Shopify admin - Analytics dashboard to work.
For information, see Shopify Analytics
-
#1334
58e039d4
Thanks @blittle! - With the introduction of authenticated pages, we also now provide the ability to prevent pages from being indexed by bots. You can do so by passingnoindex
to theSeo
component:<Seo type="noindex" data={{title: 'Login'}} />
-
#1397
fbd185ab
Thanks @frehner! - ##<ProductProvider/>
and<ProductOptionsProvider/>
<ProductProvider/>
has been removed<ProductPrice/>
was the only component left that used it; now it requires adata
prop that takes in the product object
<ProductOptionsProvider/>
now maintains and provides the state thatuseProductOptions
used to keep track of by itself. This change enables you to use multipleuseProductOptions
hook calls and have them share the same state (such as selected variant, options, etc.)
-
#1403
979f8177
Thanks @frandiox! - Breaking change: ThesetLogger
andsetLoggerOptions
utilities have been removed. The same information can now be passed under thelogger
property in Hydrogen config:// App.server.jsx -import {setLogger, setLoggerOptions} from '@shopify/hydrogen'; -setLogger({ - trace() {}, - error() {}, - // ... -}); -setLoggerOptions({ - showQueryTiming: true, - showCacheControlHeader: true, - // ... -}); function App() { // ... } export default renderHydrogen(App);
// hydrogen.config.js export default defineConfig({ // ... + logger: { + trace() {}, + error() {}, + showQueryTiming: true, + showCacheControlHeader: true, + // ... + }, });
-
#1433
cd354d3a
Thanks @frandiox! - Theresponse.writeHead
method has been removed, whileresponse.status
andresponse.statusText
are now writable.function App({response}) { - response.writeHead({ - headers: {'custom-header': 'value'}, - status: 404, - }); + response.headers.set('custom-header', 'value'); + response.status = 404; }
-
#1418
512cb009
Thanks @frandiox! - Breaking change: The client configuration, including thestrictMode
option, has been moved from custom client entry handlers to the Hydrogen configuration file. If you had a custom client entry file just to pass client options, you can remove it and do the same inhydrogen.config.js
:// Custom client entry handler -renderHydrogen(ClientWrapper, {strictMode: false}); +renderHydrogen(ClientWrapper);
// hydrogen.config.jsx export default defineConfig({ + strictMode: false, });
To remove a custom client entry handler in case it's not needed anymore, delete the custom file and change
index.html
:<body> <div id="root"></div> - <script type="module" src="/src/custom-client-entry"></script> + <script type="module" src="/@shopify/hydrogen/entry-client"></script> </body>
-
#1401
335b70ce
Thanks @frandiox! - Breaking change: TheenableStreaming
config option has been deprecated. The same feature can be done directly in the app:// hydrogen.config.js export default defineConfig({ shopify: { // ... }, - enableStreaming: (req) => { - return req.headers.get('user-agent') !== 'custom bot'; - }, });
// App.server.jsx -function App() { +function App({request, response}) { + if (request.headers.get('user-agent') === 'custom bot') { + response.doNotStream(); + } return <Suspense fallback={'Loading...'}>{/*...*/}</Suspense>; } export default renderHydrogen(App);
- #1425
e213aa86
Thanks @frandiox! - Rename internal Hydrogen global variables that could conflict with third party libraries that use the same names.
- #1361
cf2ef664
Thanks @frandiox! - Improve component bundling to reduce the total amount of JS files downloaded in the browser.
- #1460
18056879
Thanks @wizardlyhel! - Fix doc links
- #1444
0b4ee487
Thanks @blittle! - Propagate a better error message when the response from the storefront API is not JSON parseable
- #1424
446c12bf
Thanks @frandiox! - Custom loggers can return promises from their methods. Hydrogen will await for them after the current request is over but before the runtime instance ends.
- #1423
aaf9efa4
Thanks @frandiox! - Workers context (e.g.waitUntil
) is now scoped to the current request instead of globally available.
- #1330
c7dc6440
Thanks @ejfranco06! - [#1245] - Generate a default srcset for an image returned by the Shopify CDN on the Image component and allow using a custom set ofwidths.
-
#930
750baf8f
Thanks @michenly! - With the introduction of authenticated pages, we also now provide the ability to prevent pages from being indexed by bots. You can do so by passingnoindex
to theSeo
component:<Seo type="noindex" data={{title: 'Login'}} />
-
#1313
ed1933e3
Thanks @frandiox! - Breaking change: Theroutes
property inhydrogen.config.js
file has been simplified. It is now a string that represents the path to the routes from the project root:// hydrogen.config.js export default defineConfig({ - routes: import('./src/routes/**/*.server.[jt](s|sx)'), + routes: '/src/routes', });
Its default value is
/src/routes
so this property can be removed when using this directory.In the object syntax version,
dirPrefix
is removed andfiles
becomes a string:// hydrogen.config.js export default defineConfig({ routes: { - files: import('./src/routes/**/*.server.[jt](s|sx)'), - dirPrefix: './src/routes', + files: '/src/routes', basePath: '/', }, });
-
#1332
5ec1bc62
Thanks @frandiox! - A newgql
utility is exported from@shopify/hydrogen
that replacesgraphql-tag
dependency when usinguseShopQuery
. It helps reducing bundle size in production when compared to the originalgraphql-tag
.Before:
import gql from 'graphql-tag'; // ... useShopQuery({ query: gql`...`, // ... });
After:
import {gql} from '@shopify/hydrogen'; // ... useShopQuery({ query: gql`...`, // ... });
- #1340
631832ec
Thanks @jplhomer! - Breaking change: Theresponse.send()
function has been removed. Useexport async function api()
to send custom responses instead.
-
#1371
84a2fd09
Thanks @frehner! - Made updates to<Image/>
:- Fixed some TypeScript type issues with Image.
data.url
andalt
are now required props in Typescript, but won't break the actual component if you don't pass them.
- #1348
211093e5
Thanks @developit! - Fix HTML double-decoding in flight response
- #1322
36bd77c4
Thanks @frandiox! - Fix server hanging in Node.js environment when not using Hydrogen Middleware.
- #1360
d9b0d03b
Thanks @blittle! - Fix a problem where encoded html content props passed from server to client components would get double decoded, and break hydration on app load.
- #1339
fef4cb84
Thanks @jplhomer! - Useimport.meta.env.DEV
instead ofprocess.env.LOCAL_DEV
to hash asset filenames and show performance metrics debugging
-
#1327
ce56311f
Thanks @frehner! - Breaking Change:<Money />
updates and<UnitPrice />
Removed.<UnitPrice/>
has been removed<Money/>
has two new props:measurement
andmeasurementSeparator
which do the work thatUnitPrice
used to do- The TypeScript types for
<Money/>
have been improved and should provide a better typed experience now
- #1216
771786a6
Thanks @wizardlyhel! - Fixes an issue where cached sub-requests were not revalidating properly.
- #1304
aa196150
Thanks @frehner! - Removed<ProductTitle/>
and<ProductDescription/>
components. To migrate, use{product.title}
and{product.description}
instead.
-
#1335
0d90f92b
Thanks @blittle! - Breaking ChangeThe
<ProductMetafield />
component has been removed. Instead, directly use the<Metafield>
component.
- #1311
3e3fd72f
Thanks @jplhomer! - Client components no longer need to use@shopify/hydrogen/client
as the import path. All Hydrogen components can now be imported from@shopify/hydrogen
regardless of their context.
-
#1259
110e9aca
Thanks @blittle! - You can now easily disable streaming on any page conditionally with theenableStreaming
option insidehydrogen.config.js
:import {CookieSessionStorage} from '@shopify/hydrogen'; import {defineConfig} from '@shopify/hydrogen/config'; export default defineConfig({ routes: import.meta.globEager('./src/routes/**/*.server.[jt](s|sx)'), shopify: { defaultLocale: 'en-us', storeDomain: 'hydrogen-preview.myshopify.com', storefrontToken: '3b580e70970c4528da70c98e097c2fa0', storefrontApiVersion: '2022-07', }, enableStreaming: (req) => req.headers.get('user-agent') !== 'custom bot', });
By default all pages are stream rendered except for SEO bots. There shouldn't be many reasons to disable streaming, unless there is a custom bot not covered by Hydrogen's bot detection.
- #1318
668a24da
Thanks @blittle! - Buffer RSC flight responses. There isn't any benefit to streaming them, because we start a transition on page navigation. Buffering also fixes caching problems on the flight response.
-
#1283
eea82cb0
Thanks @jplhomer! - Hydrogen has been updated to use the latest stable version of React.To update an existing Hydrogen app:
yarn add react@latest react-dom@latest
-
#1257
5cd7a672
Thanks @frandiox! - Support for CSS Modules has been improved. It now behaves closer to the default behavior in Vite where styles are collected automatically.Remove the
StyleTag
component that was needed before:export default function MyComponent() { return ( <div> - <myStyles.StyleTag /> <h1>Title</h1> </div> ); }
Optionally, update your wildcard imports to default or named imports:
-import * as myStyles from './my.module.css'; +import myStyles from './my.module.css'; // Or +import {red, green, blue} from './my.module.css';
-
#1271
9d0359b8
Thanks @frehner! - ##<Image/>
The
<Image/>
component and related utility functions were reworked and the following changes apply:useImageUrl
is no longer available; useshopifyImageLoader
instead, which is available to run both server- and client-side.- The TypeScript experience with
<Image/>
is improved; props will be validated better, andloader
andloaderOptions
will be better typed - When using the
src
prop,width
andheight
are now required - When using the
data
prop,data.width
anddata.height
orwidth
andheight
props are required - The
src
anddata
props are mutually exclusive - The
loader
prop now receives a singular param as an object options
has been merged withloaderOptions
. When using thedata
prop,loaderOptions
will be the options for Shopify CDN images. When using thesrc
prop,loaderOptions
will be whatever you define them to be.- The TypeScript type
ImageSizeOptions
is now namedShopifyLoaderOptions
- The TypeScript type
ImageLoaderOptions
is now namedShopifyLoaderParams
- The
priority
prop was removed; use the HTML-standardloading
prop instead
- The
<Video/>
component'soptions
props was renamed toimagePreviewOptions
to add clarity as to what the options were for. imagePreviewOptions
matches the (newly updated) shape of<Image/>
'sloaderOptions
- #1290
437b1616
Thanks @jplhomer! - Allow cart queries to be customized by adding a newcartFragment
prop toCartProvider
. Learn more.
- #1247
ee64873e
Thanks @frandiox! - Improve the way client components are discovered in order to reduce bundle sizes.
-
#1053
c407f304
Thanks @blittle! - The selected country is now persisted a part of the session. This means that the page can be refreshed and the country will still be selected. There are a few breaking changes:useCountry()
hook now only returns the currently selected country. ThesetCountry()
method has been removed.- The
useCountry()
hook expects acountryCode
andcountryName
to be a part of the user session. - The example
/countries
API route has been updated to accept aPOST
request to update the selected country. The CountrySelector components need to be updated to use that route.
// src/routes/countries.server.jsx -export async function api(request, {queryShop}) { +export async function api(request, {queryShop, session}) { + if (request.method === 'POST') { + const {isoCode, name} = await request.json(); + + await session.set('countryCode', isoCode); + await session.set('countryName', name); + + return 'success'; + } const { data: { localization: {availableCountries}, }, } = await queryShop({ query: QUERY, }); return availableCountries.sort((a, b) => a.name.localeCompare(b.name)); }
// src/components/CountrySelector.client.jsx export default function CountrySelector() { const [listboxOpen, setListboxOpen] = useState(false); - const [selectedCountry, setSelectedCountry] = useCountry(); + const [selectedCountry] = useCountry(); + const setSelectedCountry = useCallback( + ({isoCode, name}) => { + fetch(`/countries`, { + body: JSON.stringify({isoCode, name}), + method: 'POST', + }) + .then(() => { + window.location.reload(); + }) + .catch((error) => { + console.error(error); + }); + }, + [], + ); return ( ... ); }
- Each server component page that depends on the selected country pulls it from the session with
useSession()
, rather thanserverProps
.
// src/routes/products/[handle].server.jsx + import { useSession } from '@shopify/hydrogen'; - export default function Product({country = {isoCode: 'US'}}) { + export default function Product() { const {handle} = useRouteParams(); + const {countryCode = 'US'} = useSession(); ... }
- #1245
07866e82
Thanks @0x15f! - #1245 - Support optionalpriority
prop on Image component. Whentrue
, the image will be eagerly loaded. Defaults tofalse
.
- #1272
c1888652
Thanks @wizardlyhel! - Remove flight chunk
-
#1065
81ae47fd
Thanks @frandiox! - A new config filehydrogen.config.js
replaces the existingshopify.config.js
in your Hydrogen app.Hydrogen apps now expect a
hydrogen.config.js
in the root folder. This config file accepts Shopify storefront credentials, routes, session configuration, and more.To migrate existing apps, you should create a
hydrogen.config.js
(orhydrogen.config.ts
) file in your Hydrogen app:import {defineConfig} from '@shopify/hydrogen/config'; import { CookieSessionStorage, PerformanceMetricsServerAnalyticsConnector, } from '@shopify/hydrogen'; export default defineConfig({ routes: import.meta.globEager('./src/routes/**/*.server.[jt](s|sx)'), shopify: { storeDomain: 'YOUR_STORE.myshopify.com', storefrontToken: 'YOUR_STOREFRONT_TOKEN', storefrontApiVersion: '2022-07', }, session: CookieSessionStorage('__session', { path: '/', httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 60 * 60 * 24 * 30, }), serverAnalyticsConnectors: [PerformanceMetricsServerAnalyticsConnector], });
Then, update your
App.server.jsx
to remove previous arguments fromrenderHydrogen()
:import renderHydrogen from '@shopify/hydrogen/entry-server'; -function App({routes}) { +function App() { return ( <Suspense fallback={<LoadingFallback />}> - <ShopifyProvider shopifyConfig={shopifyConfig}> + <ShopifyProvider> <CartProvider> <DefaultSeo /> <Router> - <FileRoutes routes={routes} /> + <FileRoutes /> <Route path="*" page={<NotFound />} /> </Router> </CartProvider> <PerformanceMetrics /> {process.env.LOCAL_DEV && <PerformanceMetricsDebug />} </ShopifyProvider> </Suspense> ); } -const routes = import.meta.globEager('./routes/**/*.server.[jt](s|sx)'); - -export default renderHydrogen(App, { - routes, - shopifyConfig, - session: CookieSessionStorage('__session', { - path: '/', - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict', - maxAge: 60 * 60 * 24 * 30, - }), - serverAnalyticsConnectors: [PerformanceMetricsServerAnalyticsConnector], -}); +export default renderHydrogen(App);
Next, update
vite.config.js
in your app to remove references toshopifyConfig
:import {defineConfig} from 'vite'; import hydrogen from '@shopify/hydrogen/plugin'; -import shopifyConfig from './shopify.config'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [hydrogen(shopifyConfig)], + plugins: [hydrogen()],
Finally, delete
shopify.config.js
from your app.
- #1211
f3d26511
Thanks @wizardlyhel! - Build chunks are inside assets folder
-
#1215
a0ed7c06
Thanks @frehner! -useMoney
now returns two additional properties:withoutTrailingZeros
andwithoutTrailingZerosAndCurrency
<Money />
now has two additional and optional props:withoutMoney
andwithoutCurrency
.
- #1096
0a15376e
Thanks @wizardlyhel! - Make performance data available with ClientAnalytics and optional for developers to include
- #1167
0a5ac1cb
Thanks @benjaminsehl! - Only warn in console on missing Model3D alt tag, do not throw error
- #1144
dec5eb8e
Thanks @wizardlyhel! - fix 0.17 build
-
#1044
c8f5934d
Thanks @blittle! - Hydrogen now has a built in session and cookie implementation. Read more about how sessions work in Hydrogen. The starter template also includes a cookie session storage implementation. To use the new session implementation within an existing Hydrogen app:import renderHydrogen from '@shopify/hydrogen/entry-server'; import { Router, Route, FileRoutes, ShopifyProvider, + CookieSessionStorage, } from '@shopify/hydrogen'; import {Suspense} from 'react'; import shopifyConfig from '../shopify.config'; import DefaultSeo from './components/DefaultSeo.server'; import NotFound from './components/NotFound.server'; import LoadingFallback from './components/LoadingFallback'; import CartProvider from './components/CartProvider.client'; function App({routes}) { return ( <Suspense fallback={<LoadingFallback />}> <ShopifyProvider shopifyConfig={shopifyConfig}> <CartProvider> <DefaultSeo /> <Router> <FileRoutes routes={routes} /> <Route path="*" page={<NotFound />} /> </Router> </CartProvider> </ShopifyProvider> </Suspense> ); } const routes = import.meta.globEager('./routes/**/*.server.[jt](s|sx)'); export default renderHydrogen(App, { routes, shopifyConfig, + session: CookieSessionStorage('__session', { + path: '/', + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + maxAge: 60 * 60 * 24 * 30, + }), });
-
#881
a31babfb
Thanks @jplhomer! - ## Change from serverState to serverPropsBreaking changes:
useServerState()
is gone. UseuseServerProps()
insteaduseServerProps()
is reset on each page navigation. PreviouslyuseServerState()
was not.useServerProps()
does not containpathname
andsearch
. Use the useNavigate hook to programmatically navigate instead.
Explanation:
The current behavior of server state is to persist indefinitely (until a hard page reload). This works great for things like the CountrySelector, where the updated state is meant to persist across navigations. This breaks down for many other use cases. Consider a collection paginator: if you paginate through to the second page of a collection using server state, visit a product page, and then go to a different collection page, the new collection page will use that same pagination variable in server state. This will result in a wonky or errored experience.
Additionally, we have found that the term for
serverState
is confusing. The hook is used within client components, yet the state is passed as a prop to server components.As a result,
serverState
is now gone. Instead communicating between client and server components is throughserverProps
. If a client component wants to re-render server content, it just callssetServerProps('some', 'data')
. Those props will be serialized to the server, and the server component will re-render. Additionally, the server props are reset on page navigation. So that they will not bleed between pages (fixes #331).If you previously relied on
serverState
for global state in your app, you shouldn't useserverProps
anymore. Instead we'll introduce a new session based mechanism for global state (in the meantime you could manually manage a cookie).Lastly,
serverProps
no longer include thepathname
andsearch
parameters. Programmatically navigate in hydrogen instead with the useNavigate hook.
- #1098
f3dafec4
Thanks @wizardlyhel! - Obfuscate chunk filename on production build
- #1131
8199023b
Thanks @blittle! - Fix hydration issue where strings with $ would get converted to a single $
- #1105
57ececf8
Thanks @frehner! - Regenerate the graphql.schema.json which should fix the sudden end-of-line termination, and makes the schema valid again.
- #1099
6b50d371
Thanks @blittle! - Fix Hydrogen to not hard fail when client analytics doesn't load. Analytics might fail to load due to client-side adblockers.
-
#1082
bd14340c
Thanks @jplhomer! - UpdateuseUrl()
to allow a developer to subscribe to a reactive version of the current router location.Example:
import {useUrl} from '@shopify/hydrogen/client'; function MyClientComponent() { const url = useUrl(); useEffect(() => { // Record navigation, analytics, etc }, [url]); }
- #1075
05dea552
Thanks @jplhomer! - Properly set buyer IP and secret token for API Route queryShop helper
-
#983
52af261b
Thanks @jplhomer! - Introduce Suspense-friendlyfetchSync
API for server and client components.When using
fetchSync
in server components, you provide options for caching and preloading. This is similar to theuseQuery
hook:import {fetchSync, CacheMinutes} from '@shopify/hydrogen'; import {Suspense} from 'react'; export function MyServerComponent() { return ( <Suspense fallback="Loading..."> <MyThings /> </Suspense> ); } function MyThings() { const things = fetchSync('https://3p.api.com/things.json', { preload: true, cache: CacheMinutes(), }).json(); return <h2>{things.title}</h2>; }
When using
fetchSync
in client components, you cannot provide options for caching and preloading. You must importfetchSync
from@shopify/hydrogen/client
:import {fetchSync} from '@shopify/hydrogen/client'; import {Suspense} from 'react'; export function MyClientComponent() { return ( <Suspense fallback="Loading..."> <MyThings /> </Suspense> ); } function MyThings() { const things = fetchSync('https://3p.api.com/things.json').json(); return <h2>{things.title}</h2>; }
-
#890
a4c6d6c4
Thanks @wizardlyhel! - Analytics instrumentation - this provides integration points for both server and client side analytics instrumentations
-
#1057
06d92ddc
Thanks @frandiox! - Ability to concatenate requests in API route handlers without leaving the server by returning a new Request instance.// src/routes/my-page.server.jsx export async function api(request) { if (request.method === 'POST') { // do some work here... } return new Request(request.url, {method: 'GET'}); } export default function Page() { return ( <form action="/my-page" method="POST"> ... </form> ); }
In the previous example, a POST request to
/my-page
would run the API handler and automatically continue with the server component rendering (GET). This is useful for handling HTML forms without waterfall requests.
- #1049
b88a885d
Thanks @wizardlyhel! - Support sub request cache control headerstale-while-revalidate
everywhere
- #1047
5268bf85
Thanks @jplhomer! - Restore scroll position when navigating using the back and forward buttons.
- #1062
cc172ae7
Thanks @jplhomer! - Fix encoding of quotes in CSS Modules which caused hydration errors
- #1046
3947d53a
Thanks @michenly! - Fixed server Cookie bug where initializing with empty string will resulted in 1 item in the Cookies Map.
- #1072
47c0c184
Thanks @michenly! - Improve type for ShopifyContextValue to be based on ShopifyConfig.
-
#1028
ba174588
Thanks @michenly! - Starting from SF API version2022-04
, the preferred way to request translatable resources is using the@inContext
directive. See the API docs on how to do this and which resources have translatable properties.This causes a breaking change to the
useShopQuery
hook. Thelocale
property has been removed from the argument object;Accept-Language
is no longer being send with every request, and we are no longer using locale as part of the cache key.The
useShop
hook will now return thelanguageCode
key, which is the first two characters of the existinglocale
key.Both
locale
&languageCode
values are also now capitalized to make it easier to pass into a GraphQL@inContext
directive.
- #1020
e9529bc8
Thanks @jplhomer! - PreloadLink
URLs by default when a user signals intent to visit the URL. This includes hovering or focusing on the URL. To disable preloading, pass<Link preload={false} />
to the component.
- #1017
4c87fb63
Thanks @frandiox! - Do not cache Storefront API responses that contain GraphQL errors (amend previous fix).
- #1026
836b064d
Thanks @frehner! - Updated the Typescript types and GraphQL schema to the newest updates from Storefront API 2022-04. Of note in this update is the ability to skipedges
and go directly tonode
, for example:product.nodes[0]
instead ofproduct.edges[0].node
- #1032
03488083
Thanks @jplhomer! - Catch hydration errors related to experimental server components bugs and prevent them from being logged in production.
- #1007
7cfca7b0
Thanks @scottdixon! - Fix API index routes Shopify/hydrogen#562
- #1000
6d0d5068
Thanks @frandiox! - Do not cache Storefront API responses that contain GraphQL errors.
- #1003
d8a9c929
Thanks @jplhomer! - Update useShopQuery to accept a custom Storefront API secret token, and forward the Buyer IP.
-
#922
b5eaddc1
Thanks @frehner! - Fragments and their related types have been removed:- ExternalVideoFragment and ExternalVideoFragmentFragment
- Model3DFragment and Model3DFragmentFragment
- ImageFragment and ImageFragmentFragment
- MoneyFragment and MoneyFragmentFragment
- UnitPriceFragment and UnitPriceFragmentFragment
- VideoFragment and VideoFragmentFragment
- MetafieldFragment and MetafieldFragmentFragment
- Seo fragments and types:
- DefaultPageSeoFragment and DefaultPageSeoFragmentFragment
- HomeSeoFragment and HomeSeoFragmentFragment
- ProductSeoFragment and ProductSeoFragmentFragment
- CollectionSeoFragment and CollectionSeoFragmentFragment
- PageSeoFragment and PageSeoFragmentFragment
- MediaFile fragments and types:
- MediaFileFragment and MediaFileFragmentFragment
- MediaFileFragment_ExternalVideo_Fragment
- MediaFileFragment_MediaImage_Fragment
- MediaFileFragment_Model3d_Fragment
- MediaFileFragment_Video_Fragment
- ProductFragment and ProductFragmentFragment
These fragments have been removed to reduce the chances of over-fetching (in other words, querying for fields you don't use) in your GraphQL queries. Please refer to the Storefront API documentation for information and guides.
-
#912
de0e0d6a
Thanks @blittle! - Change the country selector to lazy load available countries. The motivation to do so is that a lot of countries come with the Demo Store template. The problem is 1) the graphql query to fetch them all is relatively slow and 2) all of them get serialized to the browser in each RSC response.This change removes
availableCountries
from theLocalizationProvider
. As a result, theuseAvailableCountries
hook is also gone. Instead, the available countries are loaded on demand from an API route.Migratation steps:
Create an API route to retrieve available countries:
export async function api(request, {queryShop}) { const { data: { localization: {availableCountries}, }, } = await queryShop({ query: QUERY, }); return availableCountries.sort((a, b) => a.name.localeCompare(b.name)); } const QUERY = ` query Localization { localization { availableCountries { isoCode name currency { isoCode } } } } `;
Then within your client code, query the API route with a
useEffect
hook:const [countries, setCountries] = useState([]); useEffect(() => { fetch('/api/countries') .then((resp) => resp.json()) .then((c) => setCountries(c)) .catch((e) => setError(e)) .finally(() => setLoading(false)); }, []);
See an example on how this could be done inside the Demo Store template country selector
-
#698
6f30b9a1
Thanks @jplhomer! - Basic end-to-end tests have been added to the default Hydrogen template. You can run tests in development:yarn test
Or in continuous-integration (CI) environments:
yarn test:ci
-
#932
507c5cbf
Thanks @jplhomer! - Adds CSS Modules support. Hydrogen now includes a Vite plugin that collects styles for each CSS Module and exports them to aStyleTag
component. To use CSS Modules in your Hydrogen app, you must render the style tag in the component along with your styles:import * as styles from './styles.module.css'; export default MyComponent() { return ( <div className={styles.wrapper}> // A style is rendered inline <styles.StyleTag /> <p>Hello</p> </div> ); }
Explore an example implementation of CSS Modules in GitHub.
-
#846
58c823b5
Thanks @blittle! - ## New<Route>
ComponentThe
<Route>
component is available for routes not defined by the file system. The<Route>
component must be used within the<Router>
component.// app.server.jsx function App({routes, ...serverProps}) { return ( <Suspense fallback={<LoadingFallback />}> <ShopifyProvider shopifyConfig={shopifyConfig}> <CartProvider> <DefaultSeo /> <Router serverProps={serverProps}> <Route path="/custom" page={<CustomRoute />} /> </Router> </CartProvider> </ShopifyProvider> </Suspense> ); } function CustomRoute() { return <h1>Custom route</h1>; }
<Route>
accepts two props:Property Type Required Description path
string
Yes The URL path where the route exists. The path can contain variables. For example, /products/:handle
.page
A rendered Server Component reference
Yes A reference to a React Server Component that's rendered when the route is active. You can have multiple
<Route>
and<FileRoutes>
components in your app. Hydrogen will only render one route for each request — whichever it finds first. This means the<Router>
component no longer takesfallback
as a prop. It also doesn't needserverProps
. Instead, to render a 404 "Not Found" page, add<Route path="*" page={<NotFound />} />
to your app. Make sure it's the last<Route>
defined inside your app:function App({routes, ...serverProps}) { return ( <ShopifyProvider shopifyConfig={shopifyConfig}> <CartProvider> <DefaultSeo /> - <Router - fallback={<NotFound response={serverProps.response} />} - serverProps={serverProps} - > + <Router> <FileRoutes routes={routes} /> + <Route path="*" page={<NotFound />} /> </Router> </CartProvider> </ShopifyProvider> ); }
The
<FileRoutes>
component now accepts two additional optional props:Property Type Required Default Value Description basePath
string
No "/"
A path that's prepended to all file routes. dirPrefix
string
No "./routes"
The portion of the file route path that shouldn't be a part of the URL. You need to modify
dirPrefix
if you want to import routes from a location other thansrc/routes
.You can modify
basePath
if you want to prefix all file routes. For example, you can prefix all file routes with a locale:<Router> <FileRoutes basePath={`/${locale}`} routes={routes} /> <Route path="*" page={<NotFound />} /> </Router>
You can use the
useRouteParams()
hook to retrieve the parameters of an active route. The hook is available in both server and client components:// products/[handle].server.jsx import {useRouteParams} from '@shopify/hydrogen'; export default function Product() { const {handle} = useRouteParams(); // ... }
// ProductDetails.client.jsx import {useRouteParams} from '@shopify/hydrogen/client'; export default function ProductDetails() { const {handle} = useRouteParams(); // ... }
-
#842
626e58ee
Thanks @wizardlyhel! - Removed theRawhtml
component.Upgrade your project by replacing references to the
RawHtml
component to follow React'sdangerouslySetInnerHTML
:Change all
RawHtml
component<RawHtml string="<p>Hello world</p>" />
to jsx equivalent
<div dangerouslySetInnerHTML={{__html: '<p>Hello world</p>'}} />
- #870
4c0fcd8f
Thanks @frandiox! - Remove useQuery hook from client exports to avoid leaking server logic to the browser.
-
#965
cdad13ed
Thanks @blittle! - Fix server redirects to work properly with RSC responses. For example, the redirect component within the Demo Store template needs to change:export default function Redirect({response}) { - response.redirect('/products/snowboard'); - return <div>This page is redirected</div>; + return response.redirect('/products/snowboard'); }
This server component is rendered two ways:
- When an app directly loads the redirect route, the server will render a 300 redirect with the proper location header.
- The app is already loaded, but the user navigates to the redirected route. We cannot 300 respond in this scenario, instead
response.redirect(...)
returns a component which will redirect on the client.
- #904
1b46f8d0
Thanks @wizardlyhel! - Log query key when provided in string
-
#758
0bee3af0
Thanks @frandiox! - Upgrade to React experimental version0.0.0-experimental-2bf7c02f0-20220314
.To upgrade your Hydrogen app, change the pinned version of
react
andreact-dom
in yourpackage.json
file to this version, or run:yarn add @shopify/hydrogen [email protected] [email protected]
- #895
1017b541
Thanks @frandiox! - Improve error thrown in development when entry point fails on load.
- #871
4cb07c73
Thanks @scottdixon! - Hydrogen docs: Update ProductProvider example query
-
#878
587aa3e6
Thanks @frandiox! - Fix preloading queries in workers to prevent waterfall requests.Breaking change:
fetchBuilder
no longer accepts aRequest
argument. Instead, it now accepts aurl: string
andoptions: FetchInit
:-fetchBuilder(new Request('https://my.endpoint.com', {headers: {foo: 'bar'}})); +fetchBuilder('https://my.endpoint.com', {headers: {foo: 'bar}});
- #923
993be985
Thanks @frandiox! - Provide a Logger option to use GraphQL and disable by default. Improve logging of unused query properties.
8271be8
Thanks @michenly! - Export Seo components Fragement and use them in the Demo Store template.
-
#827
745e8c0
Thanks @michenly! - Move any staticFragment
properties on components to the entry point@shopify/hydrogen/fragments
. The migration diff are as follows:- import {ExternalVideoFragment} from '@shopify/hydrogen'; + import {ExternalVideoFragment} from '@shopify/hydrogen/fragments'; - import type {ExternalVideoFragmentFragment} from '@shopify/hydrogen'; + import type {ExternalVideoFragmentFragment} from '@shopify/hydrogen/fragments';
- import {ImageFragment} from '@shopify/hydrogen'; + import {ImageFragment} from '@shopify/hydrogen/fragments'; - import type {ImageFragmentFragment} from '@shopify/hydrogen'; + import type {ImageFragmentFragment} from '@shopify/hydrogen/fragments';
- import {MediaFileFragment} from '@shopify/hydrogen'; + import {MediaFileFragment} from '@shopify/hydrogen/fragments'; - import type {MediaFileFragmentFragment} from '@shopify/hydrogen'; + import type {MediaFileFragmentFragment} from '@shopify/hydrogen/fragments';
- import {MetafieldFragment} from '@shopify/hydrogen'; + import {MetafieldFragment} from '@shopify/hydrogen/fragments'; - import type {MetafieldFragmentFragment} from '@shopify/hydrogen'; + import type {MetafieldFragmentFragment} from '@shopify/hydrogen/fragments';
- import {Model3DFragment} from '@shopify/hydrogen'; + import {Model3DFragment} from '@shopify/hydrogen/fragments'; - import type {Model3DFragmentFragment} from '@shopify/hydrogen'; + import type {Model3DFragmentFragment} from '@shopify/hydrogen/fragments';
- import {MoneyFragment} from '@shopify/hydrogen'; + import {MoneyFragment} from '@shopify/hydrogen/fragments'; - import type {MoneyFragmentFragment} from '@shopify/hydrogen'; + import type {MoneyFragmentFragment} from '@shopify/hydrogen/fragments';
- import {ProductProviderFragment} from '@shopify/hydrogen'; + import {ProductProviderFragment} from '@shopify/hydrogen/fragments'; - import type {ProductProviderFragmentFragment} from '@shopify/hydrogen'; + import type {ProductProviderFragmentFragment} from '@shopify/hydrogen/fragments';
- import {UnitPriceFragment} from '@shopify/hydrogen'; + import {UnitPriceFragment} from '@shopify/hydrogen/fragments'; - import type {UnitPriceFragmentFragment} from '@shopify/hydrogen'; + import type {UnitPriceFragmentFragment} from '@shopify/hydrogen/fragments';
- import {VideoFragment} from '@shopify/hydrogen'; + import {VideoFragment} from '@shopify/hydrogen/fragments'; - import type {VideoFragmentFragment} from '@shopify/hydrogen'; + import type {VideoFragmentFragment} from '@shopify/hydrogen/fragments';
- #455
81ac653
Thanks @johncraigcole! - Updated the ExternalVideo component to use the newembedUrl
Storefront API (introduced in 2022-04) on ExternalVideo.
-
#809
47f23f9
Thanks @frehner! - Upgrade default Storefront API to version '2022-04'. Some components have been updated to use the 2022-04 features and types as well.One important change is that the
2022-04
Storefront API no longer encodes object IDs: see more details here. Because of this, Hydrogen will no longer decode IDs, either, which will cause issues if you are using a previous version of the Storefront API with Hydrogen components.
-
#780
122a5c5
Thanks @jplhomer! - AddsqueryShop
helper to API routes. This makes it easy to query the Storefront API, similar to howuseShopQuery
is available in server components:// my-api.server.js export default function api(request, {queryShop}) { return await queryShop({ query: `query ShopName { shop { name } }`, }); }
queryShop
accepts a single argument object with the following properties:Property Type Required query
string | ASTNode
Yes variables
Record<string, any>
No locale
string
. Defaults to the locale value from the LocalizationProvider component.No Important: In order to use
queryShop
, you should passshopifyConfig
torenderHydrogen
insideApp.server.jsx
:-export default renderHydrogen(App, {routes}); +export default renderHydrogen(App, {shopifyConfig, routes});
-
#712
6368968
Thanks @blittle! - Routing in Hydrogen has been updated according to Custom Routes proposal. Specifically, a newRouter
component has been added, andDefaultRoutes
has been renamed toFileRoutes
, along with other minor changes. Custom route components are not implemented yet.Follow these steps to upgrade your
App.server.jsx
file:- Rename the parameter
pages
toroutes
when callingrenderHydrogen
. - Rename the
DefaultRoutes
component toFileRoutes
. - Add the new
Router
component as a parent ofFileRoutes
and passfallback
andserverProps
props (previously inDefaultRoutes
). - Rename
src/pages
directory tosrc/routes
and update the glob import inApp.server.jsx
toimport.meta.globEager('./routes/**/*.server.[jt](s|sx)')
.
import renderHydrogen from '@shopify/hydrogen/entry-server'; import {Router, FileRoutes, ShopifyProvider} from '@shopify/hydrogen'; import {Suspense} from 'react'; import shopifyConfig from '../shopify.config'; import DefaultSeo from './components/DefaultSeo.server'; import NotFound from './components/NotFound.server'; import LoadingFallback from './components/LoadingFallback'; import CartProvider from './components/CartProvider.client'; function App({routes, ...serverProps}) { return ( <Suspense fallback={<LoadingFallback />}> <ShopifyProvider shopifyConfig={shopifyConfig}> <CartProvider> <DefaultSeo /> <Router fallback={<NotFound />} serverProps={serverProps}> <FileRoutes routes={routes} /> </Router> </CartProvider> </ShopifyProvider> </Suspense> ); } const routes = import.meta.globEager('./routes/**/*.server.[jt](s|sx)'); export default renderHydrogen(App, {shopifyConfig, routes});
- Rename the parameter
- #850
74b14e4
Thanks @blittle! - Ignore when boomerang doesn't load. This often happens when a adblocker is present on the client. There is no longer an uncaught promise exception in the console.
- #803
7528bf4
Thanks @frandiox! - Avoid accessing undefined global __flight as a side effect of another unknown error.
-
#825
1215fdb
Thanks @michenly! -@shopify/hydrogen
will no longer export the following types- MediaFileProps
- VideoProps
- ImageProps
- ExternalVideoProps
- RawHtmlProps
- AddToCartButtonProps
- ModelViewerProps
- MoneyProps
- BuyNowButtonProps
- BuyNowButtonPropsWeControl
- ShopPayButtonProps
Any Component props type should be typed instead with
React.ComponentProps<typeof MyComponent>
.
- #792
8aad0b5
Thanks @frandiox! - Attributes from<html>
and<body>
elements inindex.html
are now included in the SSR response.
- #786
d1ecaf7
Thanks @frehner! - Updated graphql-codegen, which updates the Typescript types available for each Storefront API object
- #849
e64fa17
Thanks @blittle! - Fix server the server to only log once for the full time it takes to stream render a page
- #813
b1b959c
Thanks @frandiox! - Remove Router client-only logic from server bundle and avoid extra waterfall requests during Hydration. Extract part of the client bundle into separate modules that can be loaded in parallel.
- #761
1142647
Thanks @frehner! - Fix issue with components that take in theas
prop not validating other props when a component is passed toas
.
- #774
052f148
Thanks @frandiox! - Fix internal url usage in platforms like Vercel, which already provides protocol and host inrequest.url
.
- #775
d5b7ee1
Thanks @cartogram! - In cases where theinitialVariantId
is missing on the<ProductProvider />
, theselectedVariantId
in the returnedobject
fromuseProduct()
will now use the first available variant or the first variant (if non are available).
- #773
b6a053e
Thanks @frandiox! - Fix server bundle name in cases where CSS or images are imported in server components.
- #764
5e55da4
Thanks @wizardlyhel! - Preload queries breaking fetch on Cloudfare #764
-
#763
ea2857a
Thanks @frehner! - Client-side apps now have React'sStrictMode
component wrapping the whole app, with an option to disable it. If you do turn it off, it is recommended that you still include theStrictMode
component at as high of a level as possible in your React tree.See also React 17's docs on
StrictMode
, and React 18's updates toStrictMode
.
0.11.0 - 2022-02-24
- New React hook
useScriptLoader
is available to more easily load external scripts - Add
totalQuantity
to the returned object fromuseCart()
- Export
ProductPrice
andProductMetafield
standalone components - Added
useUrl
hook that allows the consumer to get the current url in server or client component - Added logging option
showCacheApiStatus
andcacheControlHeader
by @wizardlyhel in #472 - Pass HYDROGEN_ASSET_BASE_URL into config to set base URL for compiled assets
- Introduce Hydrogen the
<Link>
component anduseNavigate
hook for routing - Add a default virtual entry-client in
/@shopify/hydrogen/entry-client
that can be used inindex.html
- Enable early hydration when streaming
- Add variantId prop to
<ProductMetafield />
component #730 - Add query timing logging option
showQueryTiming
#699 - Add variantId prop to
<ProductPrice />
component - Add
preload
option touseQuery
anduseShopQuery
#700
<Model3D>
has been renamed to<ModelViewer>
<Product />
and<CartLine />
aliases have been removed; use the original components<ProductProvider />
and<CartLineProvider />
instead. Their nested component aliases, such as<Product.Image />
, have also been removed; in this example you should use<ProductImage />
.- Merge
/src/entry-server.jsx
entry point intoApp.server.jsx
- The following components had their prop name renamed. Refer to the documentation or #627 for more details.
<ExternalVideo />
: renamed video prop to data<Video />
: renamed video prop to data<Image>
: renamed image prop to data<MediaFile>
: renamed media prop to data<ModelViewer>
: renamed model prop to data<Metafield>
: renamed metafield prop to data<Money>
: renamed money prop to data<UnitPrice>
: renamed unitPrice prop to data, unitPriceMeasurement prop to measurement<ProductProvider>
: renamed product prop to data<CartProvider>
: renamed cart prop to data
- Helmet component has been renamed to Head
- Remove the
<SelectedVariantBuyNowButton />
component in favour of using<BuyNowButton variantId={product.selectedVariant.id} />
<SelectedVariantAddToCartButton />
has been removed; the<AddToCartButton />
will now use the selectedVariant by default.- Remove the
<SelectedVariantImage />
component in favour of using<Image data={product.selectedVariant.image} />
- Remove the
<SelectedVariantMetafield />
component in favour of using<ProductMetafield variantId={product.selectedVariant.id} />
- Remove the
<SelectedVariantShopPayButton />
component in favour of using<ShopPayButton variantId={product.selectedVariant.id} />
- Remove the
<SelectedVariantPrice/>
and<SelectedVariantUnitPrice/>
component in favour of using<ProductPrice variantId={product.selectedVariant.id} />
- Change
/react
RSC path to/__rsc
<ShopifyProvider>
can again be used in server components- Use hashes as client component ids instead of absolute paths
- Transition away from deprecated currency selector in favor of country selector
- Simplify Helmet usage and make it compatible with RSC
- The
Seo.client
component has been moved fromsrc/components
to@shopify/hydrogen
. The props of theSeo.client
component also changed to always take intype
anddata
. Refer to theSeo
component reference for more details. #539 - Standardize cache control header into caching strategies by @wizardlyhel in #629
- Target future release to use '2022-01' API Version
- Correct Typescript issue where
as
was a default prop for all components when it should not be - Update types and docs for
useCart()
hook and<CartProvider>
- Track page load performance
- The following money components no longer allow the function-as-a-child (also known as "render props") pattern; see #589
<Money>
UseuseMoney()
for customization<CartLinePrice>
UseuseMoney()
for customization<ProductPrice>
UseuseMoney()
for customization<SelectedVariantPrice>
UseuseMoney()
for customization<Metafield>
UseuseParsedMetafields()
for customization<ProductMetafield>
UseuseParsedMetafields()
for customization<SelectedVariantMetafield>
UseuseParsedMetafields()
for customization<UnitPrice>
UseuseMoney()
for customization<CartLines>
UseuseCart()
for customization
<Metafield>
now rendersratings
as a<span>
with text instead of stars;multi_line_text_field
inside of a<span>
instead of a<div>
- Use
featureImage
instead of images(first:1) on product query - Update
react-helmet-async
to 1.2.3 and remove our custom types
- Fix index routes. See #562
- Fix missing server state on SSR pass
- Fix mobile navigation in example that scrolls the body underneath when shown by @Francismori7 in #582
- Add charset to content type in HTML responses
- Fix header shift when cart is opened by @Francismori7 in #600
- Fix bug where search param is not being pass along during RSC streaming call #623
- Allow custom entry-client filenames
- Clear browser fetch cache by @wizardlyhel in #591
- Cannot redefine property error when updating client components
ShopPayButton
supports quantities greater than 1. Also fixed issues with IDs in Storefront API version 2022-01- Render error in
Gallery.client.jsx
component when product resource has an external video or no images. - Ensure youtube external videos are embed compatible urls
- Prevent client components from being cached during development
- Backticks in HTML break RSC hydration.
- and components. These components used the “function-as-a-child” pattern which doesn’t allow the
children
prop to be serialized, preventing them from being rendered within Server components.
Migration
The functionality provided by these components can be replicated using the useCartLine()
hook instead.
Example
// Before
function SomeComponent() {
return (
<>
<CartLineSelectedOptions as="ul" className="text-xs space-y-1">
{({name, value}) => (
<>
{name}: {value}
</>
)}
</CartLineSelectedOptions>
<CartLineAttributes as="ul" className="text-sm space-y-1">
{({key, value}) => (
<>
{key}: {value}
</>
)}
</CartLineAttributes>
</>
);
}
// After
function SomeComponent() {
const {merchandise} = useCartLine();
return (
<>
<ul className="text-xs space-y-1">
{merchandise.selectedOptions.map(({name, value}) => (
<li key={name}>
{name}: {value}
</li>
))}
</ul>
</>
);
}
- Remove
fetch
workaround - Remove the following hooks. (All the same functionality can be retrieved through the
useCart()
hook)useCartAttributesUpdateCallback
useCartBuyerIdentityUpdateCallback
useCartCheckoutUrl
useCartCreateCallback
useCartDiscountCodesUpdateCallback
useCartLinesAddCallback
useCartLinesRemoveCallback
useCartLinesTotalQuantity
useCartLinesUpdateCallback
useCartNoteUpdateCallback
- Remove React Router on the client
- Remove
handleEvent
in favor ofhandleRequest
- Remove
assetHandler
parameter in the newhandleRequest
<SelectedVariantAddToCartButton />
has been removed; the<AddToCartButton />
will now use the selectedVariant by default.- Remove the
<SelectedVariantImage />
component in favour of using<Image data={product.selectedVariant.image} />
- Remove the
<SelectedVariantMetafield />
component in favour of using<ProductMetafield variantId={product.selectedVariant.id} />
- Remove the
<SelectedVariantBuyNowButton />
component in favour of using<BuyNowButton variantId={product.selectedVariant.id} />
- Remove the
<SelectedVariantShopPayButton />
component in favour of using<ShopPayButton variantId={product.selectedVariant.id} />
0.10.1 - 2022-01-26
- Hot reload for newly added page files
0.10.0 - 2022-01-25
- Warn instead of error when a page server component is missing valid exports
- Adopt upstream version of React Server Components. See #498 for breaking changes
- Bump to latest version of React experimental to include upstream context bugfix
- Improve API routes by allowing strings and JS objects to be returned.
- The 'locale' option in shopify.config.js had been renamed to 'defaultLocale'
- Rename
graphqlApiVersion
tostorefrontApiVersion
inshopify.config.js
- Make sure that API routes hot reload properly
0.9.1 - 2022-01-20
- Transitive dependency bump.
0.9.0 - 2022-01-20
- API routes 🎉
- Move to undici instead of node-fetch
0.8.3 - 2022-01-13
- Add optional
locale
param touseShopQuery
to be used asAccept-Language
in the store Storefront API query - Optional purge query cache per build
- Replace log abbreviations with full text.
0.8.2 - 2022-01-07
- Warn when requests take longer than 3000ms instead of erroring
useQuery
returns an error if the query's fetch was unsuccessfuluseShopQuery
will give error hints to look atshopify.config.js
when the Storefront API responds with a 403
- Load logger only once.
- Do not attempt to decode product IDs, as they are no longer base64-encoded in
unstable
0.8.1 - 2022-01-04
- Detect bot user agents and give bots a non-streamed response.
- Add global
Oxygen.env
for server-only environment variables. - Logging abstraction with default timing information
- Upgrade to latest React 18 experimental version
- Cart decrease button removes at zero quantity
0.8.0 - 2021-12-07
- Export
CartLineSelectedOptions
properly - Fix suspense utility function
0.7.1 - 2021-12-02
- Allow
useShopQuery
to be skippable if no query is passed - Remove usage of
react-query
(Not a breaking change)
- Avoid repeating the same identifier for default and named exports
- Remove sourcemap warnings
0.7.0 - 2021-11-22
- Add file reference metafield support
- Allow custom Model3D poster
- Support synchronous server redirects
- Binding of waitUntil in playground/server-components-worker
- Default to
retry: false
inuseQuery
- Warn and ignore reserved properties in server state
- Run graphiql middleware before vite, fixing graphiql
0.6.4 - 2021-11-11
- Let Vite handle public assets in development
- New lines in hydration request break JSON.parse
- Normalize POSIX separators to support windows #201
- Scroll to top on app first load
- Update variantID to variantId #78
0.6.3 - 2021-11-10
- Add trailing slash to user components glob
0.6.2 - 2021-11-10
- Remove CartProvider from BuyNowButton
- Reading property of null for component props
- Transform deeply-imported client components
- Duplicated files and contexts in browser
0.6.1 - 2021-11-08
- Transitive dependency bump.
- Do not set headers after they are sent to client
0.6.0 - 2021-11-05
- Disable the quantity adjust button when the cart is not idle
- Use country server state in cart for the inContext directive
- Use Image url field instead of deprecated originalSrc field
- Switch to unstable API
- Update interaction prompt and interaction promp style attributes for Model3d
- Make sure all errors show an error dialog when hydrogen is in dev mode
- MediaFile component warning on non-Model3D types
- Remove console logs for caching
- Lowercased SVG tags in RSC
- Make the URL search property available via hooks
- Ensure delayed callback is fired for cache purposes in Workers runtimes.
- No updates. Transitive dependency bump.
- No updates. Transitive dependency bump.
- No updates. Transitive dependency bump.
- Update the ServerStateProvider context
- Add tabIndex to ShopPayButton
- Update LocalizationProvider query, context, and exports
- Introduct full-page and sub-request caching API.
- Update Model3D props and add binding to model-viewer events
- Add
passthoughProps.disabled
toAddToCartButton
- Do not show undefined currency symbol in production
- Add external image support to Image component
- Make
CartProvider
a client-only concern. #631 - Use
Accept: application/hydrogen
as a header when makingfetch
calls against a Hydrogen page. Useful for Custom Responses.
- Lock model-viewer.js version to 1.8
- Use the Intl.NumberFormat parts for determining the amount value returned by the useMoney hook
- Optimize React related dependencies at server start to avoid page reloads
- Do not throw when
storeDomain
contains protocol.
- Export utilities in client bundle
parseCookies
will split only on first =- Make BuyNowButton a client component since it uses useEffect
- Preserve original aspect ratio for product images
- Invoke CartProvider callbacks before performing the GraphQL mutations
- Fix the accessible label in the AddToCartButton component when an item is added to cart
- Cart fetch to return stringified error
- Remove sourcemap warnings
- Demo Store template GalleryPreview unique key warning
- Mitigation for upcoming breaking minor Vite update
- Added support for images and collections in the ProductProvider component
- Added more GraphQL fragments for building block components (Metafield, UnitPrice) and updated exports of these fragments
useQuery
now behaves exactly like react-query's hook of the same name
- Handle products with selling plans
- SSR issue when running Vite 2.6
- Occasional
ProductProviderFragment
error when booting Hydrogen dev server #571
- New GraphQL fragments for Variants, SellingPlans, and SellingPlanGroups
- Updated types for the
useProductOptions
hook
Dynamic require of "stream" is not supported
error in browser logs
- No updates. Transitive dependency bump.
- No updates. Transitive dependency bump.