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

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

Astro 還未進入穩定版本。這篇文章不會再講一次 Astro 文件提到的功能,而是文件上找不到參考,只好到處從社群爬文記下來的做法。

Astro 的標誌

"astro": "^0.26.0"

Moment 根據 Astro 的 Blog Starter 範例進行修改,基礎是 preact,使用的體感跟 React 幾乎沒有差別,只有 JSX 的 dangerouslySetInnerHtml 無法使用。

Moment 首頁截圖

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

Moment 首頁的 Lighthouse 分數


接下來的內容


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 只能讀取 .md 檔案,實際上幾乎等同於 MDX,可以輸入元件使用。

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

setup: |
  import 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} />