打造高分作品集網站超簡單:Astro 框架基礎介紹
目錄
· 什麼是 Astro?
· Island Architecture 島嶼架構
· View Transition API
· 開始開發吧!
· 結語
前言
在競爭激烈的 JavaScript 生態系中,各式各樣的框架如雨後春筍般不斷冒出,React、Vue 等「經典」框架已經不夠看了,更現代化的框架如 Svelte、Solid、Qwik 等等也受到越來越多開發者的青睞。其中,一個相當值得關注的框架就是我們今天要談的 Astro。從 NPM 的下載紀錄可以看到,Astro 近年可說是獲得了爆炸性的成長,它的高效能和友善的開發者體驗,讓它在前後端開發社群中備受矚目。
我之所以會接觸到 Astro 這個框架,也是因為看到開發者社群的討論以及 Youtube 上的大神推薦,並且也剛好也有製作作品集網站的需求,就趁著這個機會來好好的認識一下這個新框架!
什麼是 Astro?
以內容為中心
根據官方文件,Astro 將自己定位成一個旨在建立高效能、「以內容為中心 (content-focused)」的 JavaScript 框架。所謂「以內容為中心」的網頁,指的是像是部落格、行銷網站、接案公司形象網,或是電子商務平台等這種以內容展示為主、互動性較低的網頁。針對這些網頁,Astro 提供了許多常見功能的支援,例如多語系網站(i18n)、網站地圖、內容管理系統(CMS)和 Markdown/MDX 語法的原生支援。
0 JavaScript by Default
在效能方面,Astro 將所謂的「島嶼架構 (Island Architecture)」發揚光大,並預設不會載入任何的 JavaScript,讓使用者可以在網頁載入後立即與網頁互動,而不用去經歷「Uncanny Valley」這個過程 (意即雖然網頁看起來是準備好的,但實際上卻無法與之互動)。也因為不需要載入任何 JavaScript,Astro 與其他 SSR 框架相比之下,效能表現更突出。有興趣進一步閱讀的人可以參考這篇官方文章:2023 Web Framework Performance Report。
用任何你喜歡的框架
Astro 另外一個很大的賣點是,它是 「框架不可知 (framework agnostic)」 的。不像用 Next.js 需要用 React,用 Nuxt.js 就要用 Vue,Astro 並沒有和任何框架綁定,我們可以依照喜好和需求自由地使用任何前端框架,或是乾脆不要使用任何的框架。這讓原本並非前端專業的開發者,或是正在學習開發的新手可以享受它帶來的好處,卻減少學習新前端框架的知識負擔。
Island Architecture 島嶼架構
這邊要稍微介紹一下 Astro 的主要特色之一,「島嶼架構」。
「島嶼架構」其實並非 Astro 創造出來的概念,早在幾年前就已經有相關的討論和框架實現。之所以會被稱為「島嶼」,是因為我們可以把網頁中各個元件或是區域視為一個個獨立的「島嶼」,這些「島嶼」會各自處理內部的腳本載入且不會互相影響,就像是獨立的應用程式。
如下圖所示,一個網頁中可能包含了靜態的圖片、廣告,也包含了動態的側邊欄和圖片輪播,在「島嶼架構」之下,不需要與使用者進行互動的靜態內容區塊就不會經過 Hydration 的過程,我們就可以減少載入的 JavaScript 總量,提升網站的效能。動態區域的腳本也可以透過非同步的載入,讓網站更快能與使用者互動。
簡單來說,「島嶼架構」旨在尋求「高互動性的 CSR (Client-side Rendering) 應用程式」和「具有高度 SEO 表現的 SSG (Static Site Generation) 靜態網頁」之間的平衡,同時也解決 SSR (server-side rendering) 框架 Full Hydration 所帶來的問題。
Astro 所主打的「以內容為中心」,就十分適合採取這個策略,因為大部分的部落格、電商網站中的內容都是靜態的,但我們也會需要像是切換深淺色、購物車等簡單的互動。島嶼架構可以讓這些網站保持高 SEO,也能帶給使用者舒適的互動體驗。
總之,這些渲染模式和他們所帶來的效能影響並非三言兩語可以說完的,想要閱讀更多關於島嶼架構的討論,可以參考 Awesome Islands GitHub 這個 Repo,裡面集結了各種關於島嶼架構的文章和影片資源。若是對其它的渲染模式有興趣的話,也可以收藏 Patterns.dev 這個網頁。
View Transition API
Astro 另外一個值得一提的特色是「View Transition API」的應用。
有開發過 SPA (Single-page Application) 網頁的朋友應該都知道,其如同原生應用程式 (native apps) 般的即時換頁體驗是 SPA 的一個強大優勢。而 Astro 採取了較為傳統的 MPA (Multi-page Application) 模式,每次切換頁面的時候都會重新載入一個全新的頁面,在使用者體驗上就沒有 SPA 那麼流暢。
為了解決這個先天缺陷,引入瀏覽器原生的 View Transition API 可以在不增加 JS bundle 的前提之下,大大的提升 MPA 的使用者體驗。
在 Astro 3.0 版本中,View Transition API 正式成為了一個 stable feature,我們可以用短短的兩行程式碼,輕鬆將換頁過場動畫加入到我們的網頁中。從下面這個範例可以看到,MPA 要做到跟原生應用程式類似的體驗不是不可能的!建議可以直接到 Demo 操作看看會更明白實際的效果。
此外,如果想要更深入了解除了引入瀏覽器原生的 View Transition API 之外,Astro 還在底層做了哪些事來達成更好的使用者體驗,可以閱讀這篇文章:Experiments with Astro and the Shared Element Transition API。
開始開發吧!
開始一個新的 Astro 專案
首先在終端機執行以下指令來啟動一個新的 Astro 專案:
# create a new project with npm
npm create astro@latest
接著他會問你一些問題,回答完後等待所有 dependencies 下載完畢後就有一個新的 Astro 專案啦。
檔案架構與路由設定
打開新的 Astro 專案,會發現到 src
資料夾下面有三個子資料夾:pages
、layouts
和 components
,以下針對這三個資料夾做簡單的介紹:
pages
:放在 pages 資料夾中的子資料夾會自動形成網站的路由,以我的作品集為例,以下的資料夾結構就會形成https://rooturl
、https://rooturl/fr
、https://rooturl/zh
三個路徑。
layouts
: 我們可以把要應用在頁面的 layout 放在這個資料夾中,主要內容的部分則使用<slot>
作為替代。
// src/layouts/MySiteLayout.astro
---
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
const { title } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<BaseHead title={title}/>
</head>
<body>
<nav>
<a href="#">Home</a>
<a href="#">Posts</a>
<a href="#">Contact</a>
</nav>
<h1>{title}</h1>
<article>
<slot /> <!-- 主要內容會在這邊 -->
</article>
<Footer />
</body>
</html>
// src/pages/index.astro
---
import MySiteLayout from '../layouts/MySiteLayout.astro';
---
<MySiteLayout title="Home Page">
<p>My page content, wrapped in a layout!</p>
</MySiteLayout>
components
: 在這個資料夾中,我們可以放置.astro
檔案或是其他 UI 框架的檔案 (例如.vue
,.jsx
等等)
.astro
特殊語法
除了 0 JavaScript 所帶來的高速使用者體驗之外,Astro 團隊也在開發者體驗上下足了苦功,其中之一就是他們所設計的 .astro
語法。
在 .astro
檔案中,我們可以在兩個 ---
中間撰寫 JavaScript 變數,並在中間的 JSX-like HTML template 中使用。需要注意的是,在兩個 ---
中間的程式碼只會在伺服器端執行,因此我們無法在這段程式碼中撰寫互動性的效果。
若我們要為元素添加樣式,則可以直接將 css 樣式撰寫在 <style>
tag 中。寫在 <style>
中的樣式預設都是 scoped,因此不需要擔心樣式全域汙染的問題。
// src/components/MyAstroComponent.astro
---
const hello = "world"
const animals = ["Dog", "Cat", "Rabbit"];
const visible = true;
---
<h1>{hello}</h1>
<ul>
{animals.map((animal) => (
<li>{animal}</li>
))}
</ul>
<div>
{visible && <p>Show me!</p>}
{visible ? <p>Show me!</p> : <p>Else show me!</p>}
</div>
<style>
h1 {
color: red;
font-size: 3rem;
}
</style>
若要撰寫互動性的程式碼,例如添加事件監聽器,則可以使用 <script>
tag 來撰寫客戶端 JavaScript:
---
---
<button id="button">Click Me</button>
<script>
function handleClick () {
console.log("button clicked!");
}
document.getElementById("button").addEventListener("click", handleClick);
</script>
使用過 Vue 的朋友會對這樣的 script、template、style 的設計感到熟悉,而與 JSX 相似的語法則會讓使用過 React 的朋友容易上手。
Astro VSCode Extension
針對特殊的語法設計,Astro 團隊也有推出專屬的 VScode 插件,讓我們在開發 Astro 元件時可以有適當的 syntax highlight。
引入喜歡的前端框架
前面有提到,我們可以直接在 .astro
檔案中引入用其他框架所寫的元件,不過在引入之前,必須先安裝對應的 integration,假設我們要在專案中寫 React 元件:
- 執行以下指令來新增 React intergration:
npx astro add react
2. 寫好你的 React 元件,並在 .astro
檔案中引入:
// src/pages/static-components.astro
---
import MyReactComponent from '../components/MyReactComponent.jsx';
---
<html>
<body>
<h1>Use React components directly in Astro!</h1>
<MyReactComponent />
</body>
</html>
3. 要注意的是,在 Astro 中,「所有的框架元件預設都只會在伺服器端渲染」以避免將不必要的 JS 傳送到客戶端,因此所有的元件預設都是「靜態元件」!但如果我們的元件是互動性的該怎麼辦?
Astro 提供了一些特殊的 client directives 來控制我們何時要載入該元件的 JavaScript:
client:load
:當頁面載入的時候,同時載入該元件的 JS。client:visible
:只有當使用者滑動頁面到該元件進入 viewport 時,才載入該元件的 JS。client:only="framework"
:使用這個 directive 表示該元件並不會經歷在伺服器端渲染的過程,因此必須傳入對應的框架名稱以確保 JS 正確載入。
// src/pages/interactive-components.astro
---
import InteractiveButton from '../components/InteractiveButton.jsx';
import InteractiveCounter from '../components/InteractiveCounter.jsx';
import InteractiveModal from "../components/InteractiveModal.svelte"
---
<InteractiveButton client:load />
<InteractiveCounter client:visible />
<InteractiveModal client:only="svelte" />
4. 如果需要的話,我們甚至可以在同一個專案中引入以不同 UI 框架撰寫的元件 (雖然好像通常不會有這樣做的情境 🤔):
---
import MyReactComponent from '../components/MyReactComponent.jsx';
import MySvelteComponent from '../components/MySvelteComponent.svelte';
import MyVueComponent from '../components/MyVueComponent.vue';
---
<div>
<MySvelteComponent />
<MyReactComponent />
<MyVueComponent />
</div>
貼心的錯誤提示畫面
在開發者體驗方面,Astro 也很貼心的準備了友善的錯誤提示畫面,讓開發者面對錯誤時能不那麼感到懼怕!
---
const hello = "world";
const animals = ["Dog", "Cat", "Rabbit"];
---
<!-- 這邊少打了一個字母 -->
<h1>{hell}</h1>
<ul>
{animals.map((animal) => <li>{animal}</li>)}
</ul>
使用 markdown 格式文件
Markdown Frontmatter 與 Layout
既然是標榜「內容」為中心的框架,處理 markdown 格式的文件是必備的功能,而 Astro 這個部分也整合得非常好,可以直接在資料夾中新增 markdown 格式文件並引入,不需要額外的設定。例如我們在 /src/pages/
中直接新增一個 post-1.md
的文件如下:
---
layout: ../../layouts/BlogPostLayout.astro
title: Astro in brief
author: Himanshu
description: Find out what makes Astro awesome!
---
# Hi there!
This Markdown file creates a page at `your-domain.com/page-1/`
It probably isn't styled much, but Markdown does support:
- **bold** and _italics._
- lists
- [links](https://astro.build)
- and more!
在兩個 ---
之間的是我們文章的 frontmatter,可以將文章的重要訊息與主要的內容分開,在下方我們則能直接使用 markdown 語法來撰寫文章內容。注意到最上面我們引入了一個 layout 檔案,它被我們定義在 /src/layouts
中:
// src/layouts/BlogPostLayout.astro
---
const {frontmatter} = Astro.props;
---
<html>
<!-- ... -->
<h1>{frontmatter.title}</h1>
<h2>Post author: {frontmatter.author}</h2>
<p>{frontmatter.description}</p>
<slot /> <!-- Markdown content is injected here -->
<!-- ... -->
</html>
定義在 markdown 文件上方的 frontmatter 可以透過 props 的形式取出,並在 layout 中使用。此外,我們也能直接在 <style>
tag 中撰寫 markdown 文件的 CSS 樣式。
Content Collections
在 Astro 2.0 推出了 Content Collections 的功能,整合了 Zod Schema Validation,讓我們用 type safe 的方式處理 markdown 內容。若要使用 Content Collections 的方式來管理 markdown 文件,須將文件放置在特殊的資料夾 src/content
中,並指定該 Collection 的名稱如下:
接著,我們可以定義一個 src/content/config.ts
設定檔,為 Collection 加上 schema checking。如此一來,Astro 就會自動檢查文件內容是否符合 schema,在開發時也會有屬性的智慧提示。 (註:這個 config 檔案是非必要的,但如果要對文件採取檢查的話就需要設定!):
// 1. Import utilities from `astro:content`
import { z, defineCollection } from 'astro:content';
// 2. Define a `type` and `schema` for each collection
const blogCollection = defineCollection({
type: 'content', // v2.5.0 and later
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
image: z.string().optional(),
}),
});
// 3. Export a single `collections` object to register your collection(s)
export const collections = {
'blog': blogCollection,
};
Build and Deploy
因為預設是 SSG 靜態網頁,因此在打包建置完成後可以輕鬆的部屬到任何靜態部屬平台,如 github pages、Netlify 等等。如果要使用 SSR 產生動態內容的話,則必須額外整合 Adapter 並部屬到相關平台,詳情請見官方文件。這邊將以 Netlify 為例,示範部屬靜態網頁的過程:
- 至 Netlify 官方網站上註冊/登入,並進入帳號儀表板
- 在
Sites
區塊點選Add new site
,並在下拉選單選取Import an existing project
- 選擇專案的 repository 引入,Netlify 會自動偵測專案內容並填入適當的部屬設定。我們要確認以下的設定正確無誤:
- Build Command:
astro build
ornpm run build
- Publish directory:
dist
4. 接著等待網站部屬完成,就能在網站頁面看到公開網址、部屬紀錄等資訊,並且能在 Site configuration
中做進一步的設定
5. 下一次只要有新的 commit 被推上該 repo,Netlify 就會重新自動部屬
其他
礙於文章的篇幅,沒辦法在這邊介紹所有的 Astro 特色和開發基礎,若有任何疑問都建議先參考 Astro 的官方文件,編寫的非常清晰易懂。此外這邊提幾個我在開發作品集網站時有參考的內容:
- 整合 Tailwind CSS:https://docs.astro.build/en/guides/integrations-guide/tailwind/
- 圖片優化:https://docs.astro.build/en/guides/images/#_top
- Icon 插件:https://github.com/natemoo-re/astro-icon?tab=readme-ov-file
- i18n 多語系開發:https://docs.astro.build/en/recipes/i18n/#_top
- 深色模式開發:https://www.kevinzunigacuellar.com/blog/dark-mode-in-astro/
結語
Astro 的未來發展
Astro 1.0 最早在 2022 年推出,兩年後的今天已經推出了 4.0 版本,今年更計畫推出專屬的部屬服務 Astro Studio,未來發展相當值得期待。
之所以會有這麼快速的成長,一方面是找到了明確的定位 (以內容為中心) 以及相對應的技術策略 (0 JS、島嶼架構),一方面也因為採取了「framework agnostic」,吸引了很多本來只專注在單一框架的前端工程師、或是未曾使用前端框架的其他網頁工程師。
此外,前端圈近期吹起的「back to server trend」(例如關於 React Server Components, HTMX 的討論) 也讓人反思前端開發中,大量的 JS 是否有必要,畢竟除了少數的複雜網頁應用,例如 Facebook、Figma 等,這世界上大多數的網頁都是靜態網頁!雖然這並不代表未來前端工程師的職業就會消失或被取代,但身為前端工程師,需要謹記在心的是在 JavaSciprt、React 的世界之外還有好大的一片天地值得去探索。
個人使用感想
回到個人的使用體驗,我認為 Astro 對於初學者來說相當友善,就算只會原生的 HTML、CSS、JS 也可以掌握現代化的元件化開發流程,而不用先去鑽研各個框架的特殊語法和架構。
不過,雖然在基本的架構上可以很快速的開發高效能的靜態網站,但實際開發上仍有一些限制需要注意。例如,使用不同的框架時,相關套件未必相容,同一功能可能會需要不同的解決方案。例如,我的作品集的 i18n 套件只適用於 Astro 元件,無法在 React 元件中使用;或是 Astro 的專屬 Image 元件,在 React 元件中的圖片就無法享受該元件帶來的優化效果。
總而言之,Astro 仍然是個相當年輕的框架,生態系仍然在發展中,資源不像成熟框架如 Next、Nuxt 豐富,框架本身變化也很快,在一年內從 2.0 升到 4.0。但如果看完文章後,對於 Astro 的架構、效能和開發者體驗有興趣、或是正好有開發靜態網站需求的人,推薦現在就實際去開啟一個新的專案來試看看吧!
參考文章與資源
- Astro 官方文件
- 2023 Web Framework Performance Report
- Awesome Islands GitHub
- Frontend Performance: React SSR and the Uncanny Valley
- Patterns.dev
- CSR、SSR、SSG: 你需要知道的三種網頁渲染方式
- Smooth and simple transitions with the View Transitions API
- Experiments with Astro and the Shared Element Transition API
- Server Components — Next.js
- A First Look at HTMX and How it Compares to React