Releases: Shopify/hydrogen
@shopify/cli-hydrogen@4.2.1
demo-store@1.0.0
Major Changes
- All routes were changed from having a
$lang
path parameter to having a$locale
path parameter. See #860 for more details. (#864) by @frehner
Patch Changes
-
Add
.shopify
to the .gitignore file to support upcoming CLI changes (#784) by @graygilmore -
Move GraphQL fragments from the beginning of the template literal to the end of it, so that we don't get the EOF error in VSCode. (#833) by @frehner
-
Updated Tailwind configuration file with a new dynamic opacity placeholder for colors (#851) by @blanklob
-
Updated dependencies [
685bb696
,025385b6
,35a87107
,33f33edd
,0a009a3b
,9c2e67c5
,9c2e67c5
,3d458e2b
]:- @shopify/cli-hydrogen@4.1.2
- @shopify/remix-oxygen@1.0.6
- @Shopify/hydrogen@2023.4.1
demo-store@0.2.1
@shopify/hydrogen@2023.4.0
Major Changes
-
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 @benjaminsehlMigrating to the new
Image
The 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).
Examples
Basic Usage
<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;" />
Fixed-size Images
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).Custom Loaders
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
Additional changes
-
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
Patch Changes
@shopify/hydrogen-react@2023.4.0
Major Changes
-
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 @benjaminsehlMigrating to the new
Image
The 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).
Examples
Basic Usage
<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;" />
Fixed-size Images
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).Custom Loaders
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
Additional changes
-
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
@shopify/create-hydrogen@4.1.1
Patch Changes
- Updated dependencies [
2039a4a
]:- @shopify/cli-hydrogen@4.1.1
@shopify/cli-hydrogen@4.1.1
demo-store@0.2.0
Minor Changes
-
Fix scroll issues on Product Detail Page for small screens (#782) by @lifeiscontent
-
Fix Layout title on mobile when title is long (#781) by @lifeiscontent
Patch Changes
-
Adopt Remix
v2_meta
future flag (#738) by @wizardlyhelv2_meta
migration steps-
For any routes that you used
meta
route export, convert it to theV2_MetaFunction
equivalent. Notice that the package name in the import statement has also changed to'@remix-run/react'
:- import {type MetaFunction} from '@shopify/remix-oxygen'; + import {type V2_MetaFunction} from '@remix-run/react'; - export const meta: MetaFunction = () => { + export const meta: V2_MetaFunction = () => { - return {title: 'Login'}; + return [{title: 'Login'}]; };
-
If you are using data from loaders, pass the loader type to the
V2_MetaFunction
generic:- export const meta: MetaFunction = ({data}) => { + export const meta: V2_MetaFunction<typeof loader> = ({data}) => { - return {title: `Order ${data?.order?.name}`}; + return [{title: `Order ${data?.order?.name}`}]; };
-
If you are using
meta
route export inroot
, convert it to Global Meta// app/root.tsx - export const meta: MetaFunction = () => ({ - charset: 'utf-8', - viewport: 'width=device-width,initial-scale=1', - }); export default function App() { return ( <html lang={locale.language}> <head> + <meta charSet="utf-8" /> + <meta name="viewport" content="width=device-width,initial-scale=1" /> <Seo /> <Meta />
-
-
Adopt
v2_routeConvention
future flag (#747) by @wizardlyhelv2_routeConventions
migration stepsRemix v2 route conventions are just file renames. We just need to ensure when changing file name and file location, the import paths of other files are also updated.
Go to Remix docs for more details on the V2 route convention.
Rename and move the following files in the
routes
folder to adopt to V2 route convention.Before After (V2 route convention) app/routes/ ├─ [sitemap.xml].tsx ├─ [robots.txt].tsx └─ ($lang)/ ├─ $shopid/orders/$token/ │ └─ authenticate.tsx ├─ account/ │ ├─ __private/ │ │ ├─ address/ │ │ │ └─ $id.tsx │ │ ├─ orders.$id.tsx │ │ ├─ edit.tsx │ │ └─ logout.ts │ └─ __public/ │ ├─ recover.tsx │ ├─ login.tsx │ ├─ register.tsx │ ├─ activate.$id.$activationToken.tsx │ └─ reset.$id.$resetToken.tsx ├─ api/ │ ├─ countries.tsx │ └─ products.tsx ├─ collections/ │ ├─ index.tsx │ ├─ $collectionHandle.tsx │ └─ all.tsx ├─ journal/ │ ├─ index.tsx │ └─ $journalHandle.tsx ├─ pages │ └─ $pageHandle.tsx ├─ policies/ │ ├─ index.tsx │ └─ $policyHandle.tsx ├─ products/ │ ├─ index.tsx │ └─ $productHandle.tsx ├─ $.tsx ├─ account.tsx ├─ cart.tsx ├─ cart.$lines.tsx ├─ discount.$code.tsx ├─ featured-products.tsx ├─ index.tsx └─ search.tsx
app/routes/ ├─ [sitemap.xml].tsx ├─ [robots.txt].tsx ├─ ($lang).$shopid.orders.$token.authenticate.tsx ├─ ($lang).account.address.$id.tsx ├─ ($lang).account.orders.$id.tsx ├─ ($lang).account.edit.tsx ├─ ($lang).account.logout.ts ├─ ($lang).account.recover.tsx ├─ ($lang).account.login.tsx ├─ ($lang).account.register.tsx ├─ ($lang).account.activate.$id.$activationToken.tsx ├─ ($lang).account.reset.$id.$resetToken.tsx ├─ ($lang).api.countries.tsx ├─ ($lang).api.products.tsx ├─ ($lang).collections._index.tsx ├─ ($lang).collections.$collectionHandle.tsx ├─ ($lang).collections.all.tsx ├─ ($lang).journal._index.tsx ├─ ($lang).journal.$journalHandle.tsx ├─ ($lang).pages.$pageHandle.tsx ├─ ($lang).policies._index.tsx ├─ ($lang).policies.$policyHandle.tsx ├─ ($lang).products._index.tsx ├─ ($lang).products.$productHandle.tsx ├─ $.tsx ├─ ($lang)._index.tsx ├─ ($lang).account.tsx ├─ ($lang).cart.tsx ├─ ($lang).cart.$lines.tsx ├─ ($lang).discount.$code.tsx ├─ ($lang).featured-products.tsx └─ ($lang).search.tsx
Optional
If you want to continue using nested folder routes but have the
v2_routeConvention
flag turned on, you may consider using the npm package@remix-run/v1-route-convention
.If you like the flat route convention but still wants a hybrid style of nested route folder, you may consider using the npm package
remix-flat-routes
-
Adopt Remix
unstable_tailwind
andunstable_postcss
future flags for the Demo Store template. (#751) by @frandioxunstable_tailwind
andunstable_postcss
migration steps-
Move the file
<root>/styles/app.css
to<root>/app/styles/app.css
, and remove it from.gitignore
. -
Add
"browserslist": ["defaults"]
to yourpackage.json
, or your preferred value from Browserslist. -
Replace the
build
anddev
scripts in yourpackage.json
with the following:Before
"scripts": { "build": "npm run build:css && shopify hydrogen build", "build:css": "postcss styles --base styles --dir app/styles --env production", "dev": "npm run build:css && concurrently -g --kill-others-on-fail -r npm:dev:css \"shopify hydrogen dev\"", "dev:css": "postcss styles --base styles --dir app/styles -w", ... }
After
"scripts": { "dev": "shopify hydrogen dev", "build": "shopify hydrogen build", ... }
You can also remove dependencies like
concurrently
if you don't use them anywhere else. -
-
Forwards search params of
/discount/<code>
route to a redirect route. (#766) by @lneicelis -
Carts created in liquid will soon be compatible with the Storefront API and vice versa, making it possible to share carts between channels. (#721) by @scottdixon
This change updates the Demo Store to use Online Store's
cart
cookie (instead of sessions) which prevents customers from losing carts when merchants migrate to/from Hydrogen. -
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.
-
Updated CLI prompts. It's recommended to update your version of
@shopify/cli
to3.45.0
when updating@shopify/cli-hydrogen
. (#733) by @frandiox"dependencies": { - "@shopify/cli": "3.x.x", + "@shopify/cli": "3.45.0", }
-
Adopt Remix
v2_errorBoundary
future flag (#729) by @wizardlyhelv2_errorBoundary
migration steps-
Remove all
CatchBoundary
route exports -
Handle route level errors with
ErrorBoundary
Before:
// app/root.tsx export function ErrorBoundary({error}: {error: Error}) { const [root] = useMatches(); const locale = root?.data?.selectedLocale ?? DEFAULT_LOCALE; return ( <html lang={locale.language}> <head> <title>Error</title> <Meta /> <Links /> </head> <body> <Layout layout={root?.data?.layout}> <GenericError error={error} /> </Layout> <Scripts /> </body> </html> ); }
After:
// app/root.tsx import {isRouteErrorResponse, useRouteError} fr...
-
@shopify/remix-oxygen@1.0.5
Patch Changes
-
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.
@shopify/hydrogen@2023.1.7
Patch Changes
-
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/hydrogen-react@2023.1.8