使用 Astro 打造速度飛快的靜態網站

在蓋「Moment:看電影看劇時,聽到喜歡的音樂」專案的時候,覺得輕量級的網站,用 Markdown 語法可以滿足快速、便於更新內容的需求,找到了 Astro 和 Remix

Astro 目前以幾乎每天更新一次的頻率快速發展中,2022 年 8 月進入 1.0 穩定版本。這篇文章不會再講一次 Astro 文件提到的功能,而是文件上找不到參考,只好到處從社群爬文記下來的做法。

Astro 的標誌

"astro": "^1.1.0"

Moment 是搭配 Solid.js,用來處理影片播放互動,後來 Astro 本身幾乎是隱形,跟框架整合得不錯。

Moment 的 Queen: Bohemian Rhapsody

首頁有 30 張,平均 24 KB 的 JPG 圖檔,依然可以在 Lighthouse 獲得 4 個 100 綠燈。(慶祝動畫怎麼不見了?)

Moment 首頁截圖

Moment 首頁的 Lighthouse 分數

如果是從 Astro 的 Blog Starter 範例進行修改,基礎是 preact,使用的體感跟 React 幾乎沒有差別。


接下來的內容


Astro v.s. Remix

Remix 的開發體驗確實很棒,社群也非常活絡。但畢竟是全端套件,如果只是要做靜態網站,以後會優先選擇 Astro。

Astro

Remix


區分 <body> 樣式

Astro 的 CSS 是 scoped,不同頁的樣式不會混在一起。我想要首頁跟內文頁的外觀、版面不同。因此在各自頁面的 <body> 設定:

// pages/index.astro
body {
  background-color: lavender;
}

// layouts/BlogPost.astro
body {
  background-color: thistle;
}

發現首頁的 <body> 樣式,會被內頁的設定覆蓋:

首頁的 body 樣式被內文頁覆蓋

詢問過 Astro 製作團隊,是他們刻意這樣設計,如果也有不同 <body> 樣式需求,有以下 3 個步驟:

<body> 加上屬性來區別頁面

<!-- pages/index.astro -->
<body data-body-style="home">
...
</body>

<!-- layouts/BlogPost.astro -->
<body data-body-style="post">
...
</body>

import 匯入全域 CSS 檔案

// Head.astro
import '~/styles/global.css';

設定個別樣式

// ~/styles/global.css
[data-body-style="home"] {
  background-color: lavender;
}

[data-body-style="post"] {
  background-color: thistle;
}

內容頁,或者說 /layouts 裡的樣式,就不會蓋掉首頁的。


設定 Markdown 語法的樣式

在 Astro 的 pages/ 下,無論是使用 .md.mdx 檔案,都可以有等同於 MDX 的功能,將元件輸入至內文。

// LyricSection.astro
<section class="lyricSection">
  <slot />
</section>
---
# /pages/bohemian-rhapsody.mdx

layout: LyricSection from '../../components/LyricSection.astro';
---

<LyricSection>

Is this the real life?

Is this just fantasy?

Caught in a landslide

No escape from reality

</LyricSection>

這一段會產生 HTML 語法:

<!-- /bohemian-rhapsody -->
<section class="lyricSection astro-SNPOEJMT">
  <p>Is this the real life?</p>
  <p>Is this just fantasy?</p>
  <p>Caught in a landslide</p>
  <p>No escape from reality</p>
</section>

我想要設定 <section> 下面的 <p> 樣式,會發現也是因為 scoped 的緣故,沒辦法直接指定:

// LyricSection.astro
...
<style lang="scss">
.lyricSection {
  display: grid;
  row-gap: 16px;

  p {
    margin: 0;
    color: hsl(var(--color-midnight1600));
    font-size: 1.6rem;
    line-height: 24px;
  }
}
</style>

必須加上 :global() 才有效果:

// LyricSection.astro

.lyricSection {
  ...

  :global(p) {
    ...
  }
}

加入結構化資料 JSON LD 語法

按照直覺把結構化資料放在 <head> ... </head> 裡面時,會發現無法如預期輸出 JSON LD 語法:

// BaseHead.astro
---

const schema = JSON.stringify({
  '@context': 'https://schema.org',
  '@graph': [{
    '@type': 'WebPage',
    url: `${import.meta.env.PUBLIC_HOSTNAME}${canonicalPath}`,
    name: title,
    datePublished: published,
    dateModified: modified
  }]
});
---

<script type="application/ld+json">{schema}</script>

0.23.0 版開始,在 <script> 得要讓 HTML 有 Escape 過,才會出現有效的 JSON LD 語法:

// BaseHead.astro
<script type="application/ld+json" set:html={schema} />

遇到的 Bug

在程式碼區塊使用環境變數

在上一段內容,程式碼區塊裡有提到 Astro 的環境變數

上傳到正式環境才發現:Markdown 區塊裡應該要直接顯示環境變數原始碼的地方,卻把它轉換了,或是出現令人不解的訊息,在使用 SSR 的情況下,甚至會無法 Build 成功。

這個問題已經在 @astrojs/mdx@0.11.2 修正。

把環境變數顯示出來的 Markdown 程式碼區塊