跳至主要内容
Deno 2 终于来了 🎉️
了解更多
An ecommerce site with a perfect lighthouse score.

如何使用 Lighthouse 得分完美的分数构建一个电子商务网站

如今的消费者比以往任何时候都更加苛刻,尤其是在网上购物方面。这些体验必须让人感觉直观且快速。即使是 加载时间延迟 100 毫秒也会使转化率降低 7%

我们使用 商品商店 (此处为源代码),它使用 FreshShopify 的店面 API 构建,是服务器端渲染的 (SSR),包含一些 交互岛屿,并且部署在靠近用户的边缘。只发送客户端所需的内容可以使网站保持精简和快速,并获得完美的 Lighthouse 分数

A perfect mobile lighthouse score

本教程介绍了如何使用 Fresh 和 Shopify 构建一个 Lighthouse 得分完美的分数的电子商务网站。

Fresh

我们使用了 Fresh,这是一个边缘优先的 Web 开发框架,默认情况下不会向客户端发送任何 JavaScript。在需要交互性的情况下,例如产品详情页面的图片轮播或购物车,Fresh 使用岛屿架构,在组件基础上发送客户端 JavaScript。

我们产品详情页面的岛屿 除了某些交互岛屿外,一切都作为静态 HTML 进行服务器端渲染。

对于商店后端,我们使用了 Shopify。它的店面 API 可以检索库存数据,跟踪用户的购物车,并处理结账和支付。

Fresh 使用文件系统路由系统。为了更好地理解它的工作原理,让我们看一下目录结构。

merch/
├── README.md
├── deno.json
├── dev.ts
├── fresh.gen.ts
├── import_map.json
├── main.ts
├── components
├── islands
│   ├── AddToCart.tsx
│   ├── Cart.tsx
│   └── ProductDetails.tsx
├── routes
│   ├── api
│   │   └── shopify.ts
│   ├── products
│   │   └── [product].tsx
│   ├── _app.tsx
│   └── index.tsx
├── static
│   ├── favicon.ico
│   └── logo.svg
└── utils

routes/islands/ 是两个主要子目录,其中包含服务器端渲染和岛屿的逻辑。

路由

routes/ 文件夹中的每个 .tsx 文件都服务器端渲染一个页面或公开一个 API 端点。

首页或 https://merch.deno.comindex.tsx。当发出请求时,边缘服务器在 handler 函数中从 Shopify 检索数据,然后通过 Home 组件进行渲染。

products/[product].tsx 文件动态生成一个服务器端渲染的产品页面。路径中 [product] 的值通过参数 ctx (ctx.params.product) 在 handler 函数中访问。例如,当对 https://merch.deno.com/products/sticker-sheet 发出请求时,handler 函数获取值 sticker-sheet,从 Shopify 检索产品数据,然后通过 ProductPage 组件进行渲染。

最后,api/shopify.ts 公开更新购物车的编程访问权限。每次用户修改购物车时,请求都不会直接从用户的浏览器发送到 Shopify。相反,请求会通过此端点,然后由它处理并转发到 Shopify。(有关此内容的更多信息,请参见下面的 Shopify 部分。)

岛屿

电子商务网站不能完全是静态的,因为某些组件(如购物车)需要交互性。我们可以在 islands/ 中添加这些交互式组件。

例如,在每个产品页面上,当用户单击箭头时,图像会旋转

Image carousel on the product page

此客户端渲染行为在 ProductDetails.tsx 中定义,我们在其中声明 changeImage 函数并将该函数绑定到 ProductDetails 组件中的 onClick

类似地,其他交互式元素包括添加到购物车和更新购物车,其客户端逻辑分别可以在 AddToCart.tsxCart.tsx 中找到。在每个岛屿组件中,我们定义函数并将它们绑定到 onClick 监听器。

请注意,为了管理客户端上的状态,我们从 preact/hooks 中导入并使用 useState,这会在状态更改时触发重新渲染。

Shopify 店面 API

此店面与 Shopify API 交互的两种主要方式。

首先是从 Shopify 检索库存数据。这是一个简单的 graphql GET 请求,它在每个 /routes 组件文件中的 handler 函数中执行

// ./routes/index.tsx

const q = `{
  products(first: 10) {
    nodes {
      id
      handle
      title
      featuredImage {
        url(transform: {preferredContentType: WEBP, maxWidth:400, maxHeight:400})
        altText
      }
      priceRange {
        minVariantPrice {
          amount
          currencyCode
        }
        maxVariantPrice {
          amount
          currencyCode
        }
      }
    }
  }
}`;

export const handler: Handlers<Data> = {
  async GET(_req, ctx) {
    const data = await graphql<Data>(q);
    return ctx.render(data); // This function passes `data` to the component.
  },
};

第二是更新 Shopify 的购物车。这分为三个部分

  • graphql 包装函数 (/utils/shopify.ts),它向请求添加身份验证和其他相关标头
  • 一些 /utils/data.ts 中的辅助函数,它将查询抽象成人类可读的函数
  • 我们的端点 /routes/api/shopify.ts,它接收来自客户端的查询和输入(例如,当用户将产品添加到购物车时),并通过 graphql 包装函数创建对 Shopify API 的请求,该函数向请求添加身份验证,并最终将其发送到 Shopify 的 API。

当用户与购物车进行交互时,相关的 islands/ 组件将调用辅助函数(例如 addToCart)。该函数将使用相应的查询和负载调用我们的 /api/shopify 端点,然后调用我们的 graphql 包装函数,该函数向请求添加身份验证,并最终将其发送到 Shopify 的 API。

图像优化

为了获得完美的 Lighthouse 分数,务必注意从服务器接收的每个文件的大小。

Shopify 的店面 API 可以 对我们的图像应用转换。在下面这段 graphql 查询代码片段中,我们请求 尺寸为 400x400、内容类型为 WEBP 的图像。

featuredImage {
  url(transform: {preferredContentType: WEBP, maxWidth:400, maxHeight:400})
  altText
}

将您的网站部署到靠近用户的服务器

您可能拥有最快的商店,但如果您的用户距离您的服务器数千英里,那么您网站的首次字节时间仍然会受到光速的影响。

为了进一步减少延迟,我们使用 Deno Deploy 在全球范围内将我们的商品商店托管在边缘。

每次有人访问我们的商店,最近的边缘服务器都会收到一个GET请求,向Shopify请求相关数据,将其渲染成HTML,然后发送回来。凭借34个全球位置,任何用户距离您的网站都不会超过几毫秒。

看看在Shopify中更新产品后,我们的商店显示新产品图片的速度有多快

Updating an image on Shopify, then refreshing merch store

设置Deno Deploy就像连接您的GitHub、选择仓库和入口文件一样简单

Connecting GitHub and selecting the “merch” repo

每次您合并到主分支,它都会在几秒钟内在Deno Deploy上更新。

接下来是什么?

虽然构建网页变得更容易,但在某些方面它比以往任何时候都更加复杂。我们必须支持各种屏幕尺寸和互联网速度。虽然我们无法控制用户是在笔记本电脑上还是在隧道里的火车上,但我们可以控制从服务器发送的内容。

我们已经构建并开源了我们的商品商店,向每个人展示创建一个拥有完美 Lighthouse 得分的电子商务商店是多么简单(而且有趣!)。

您使用Deno或Fresh构建吗?在DiscordTwitter上与我们分享!