Hydrogen 是 Shopify 打造的一個基於 React 的 Framework,可以用來製作客製化的店面網站,其最大的賣點是支援 React Server Components(RSC),這應該算是全球走在最前沿的 RSC 大型實驗。
Hydrogen 依賴在一些蠻知名的套件/技術之上,包括:Vite、GraphQL、React Router。它也提供一些 Components、Hooks、Utilities 來幫助店面網站開發。
Hydrogen 架構
從這張圖可以看到,Hydrogen 做的網站可以跑在 Node.js 上,也可以跑在 Worker 上(Shopify 的 Oxygen 平台或是 Cloudflare Worker)。
開發的時候使用 Vite Development Server,會有處理 cache 的 layer,再來就是進到 entry-server,主要的 App component,再看是要 render 哪個 page。
使用 create-hydrogen-app
建出來的 folder,裡面會有這樣的結構:
└── src
├── components
└── Button.client.jsx
└── Cart.client.jsx
└── CartIcon.client.jsx
└── ...
├── pages
└── collections
└── [handle].server.jsx
└── pages
└── [handle].server.jsx
└── products
└── [handle].server.jsx
└── index.server.jsx
└── sitemap.xml.server.jsx
├── App.server.jsx
├── entry-client.jsx
├── entry-server.jsx
├── index.css
其中包括 file-based routing system、主要的 App component — App.server.jsx
、以及 entry-client.jsx
和 entry-server.jsx
兩個進入點。
Server Components
學習 React Server Components(RSC),是理解 Hydrogen 的其中一個環節,我之前有寫了一篇「React 新概念 — Server Components」的介紹,有興趣可以看看。
簡單來說,component 必須被分成:
.server.jsx
— 在 server 上 render,不能使用 client-only feature.client.jsx
— 在 client 上 render,不能使用 server-only feature.jsx
— 可以同時在 client 跟 server 上 render
React core team 之前只做了 Webpack 的 demo,Hydrogen 能支援 RSC,是依靠 Shopify 自行開發了 Vite 上的實作。
所有 route 的部分,都是使用 RSC,例如:
└── src
├── pages
└── products
└── [handle].server.jsx
└── index.server.jsx
這樣子的檔案可以各自對應到:
pages/index.server.jsx
— localhost:3000/pages/products/[handle].server.jsx
— localhost:3000/products/<handle>
這些 page server component 會接收 request
、 response
作為 props,可以針對它們作出處理:
function MyPage({ request, response }) {
if (request.headers.get('my-custom-header') === SOME_VALUE) {
// Do something based on a header
} response.headers.set('custom-header', 'value'); // ...
}
在 client 用 setServerState
設定的 server state 也會傳遞到 page server components 當作 props:
function MyPage({ custom, state, here }) {
// Use custom server state
}
server state 是一種 client component 跟 server component 溝通 state 的機制。例如,這個 MyPage
server component 接收一個 selectedProductId
prop:
export default function MyPage({ selectedProductId }) {
const { data } = useShopQuery({
query: QUERY,
variables: { productId: selectedProductId },
});
const { product } = data; return (
<>
<div>Selected product is {product.title}</div>
<ProductSelector selectedProductId={selectedProductId} />
</>
);
}
在 client component 可以用 useServerState
hook 來取得 setServerState
,並在 onClick
的時候用 setServerState
來設定 MyPage
的selectedProductId
來重新 render server component:
import { useServerState } from '@shopify/hydrogen/client';export default function ProductSelector({ selectedProductId }) {
const { setServerState } = useServerState(); return (
<div>
<button
onClick={() => {
setServerState('selectedProductId', 123);
}}
>
Select Shoes
</button>
<button
onClick={() => {
setServerState('selectedProductId', 456);
}}
>
Select Dresses
</button>
</div>
);
}
流程會是這個樣子:
好用的 useShopQuery
Shopify 本來就有提供相當好用的 GraphQL API,因此 Hydrogen 也提供了一個 useShopQuery
可以輕鬆地做查詢:
import { useShopQuery } from '@shopify/hydrogen';
import gql from 'graphql-tag';export default function Blog() {
const { data } = useShopQuery({
query: QUERY,
variables: {
handle: 'frontpage',
},
}); return <h1>{data.blog.articles.edges[0].node.title}</h1>;
}const QUERY = gql`
query blogContent($handle: String!) {
blog: blogByHandle(handle: $handle) {
articles(first: 1) {
edges {
node {
id
title
}
}
}
}
}
`;
Caching
Hydrogen 有提供兩種不同的 cache 機制:
- 子查詢 cache(Sub-request caching)
- 全頁 cache(Full-page caching)
在使用 useShopQuery
的時候可以去設定請求的 Cache-Control:
const { data } = useShopQuery({
query: QUERY,
cache: {
// Cache the data for one second.
maxAge: 1,
// Serve stale data for up to nine seconds while getting a fresh response in the background.
staleWhileRevalidate: 9,
},
});
如果是沒有動態資料的頁面,可以進一步的使用全頁 cache:
export default function MyProducts({ response }) {
response.cache({
// Cache the page for one hour.
maxAge: 60 * 60,
// Serve the stale page for up to 23 hours while getting a fresh response in the background.
staleWhileRevalidate: 23 * 60 * 60,
});
}
介紹大概就到這裡了,未來想要做類似架構可以參考看看。