아스트로는 웹 애플리케이션 개발을 위한 안정적인 서버 측 플랫폼이고, 알파인은 미니멀리스트를 위한 대표적인 프론트엔드 자바스크립트 프레임워크다. 이 둘을 조합하면 군더더기 없는 다용도 스택이 만들어진다. 이 글에서는 아스트로와 알파인 각각의 장점을 결합해 웹 애플리케이션을 개발하는 과정을 살펴본다.
아스트로-알파인 스택의 개요
아스트로는 리액트(React), 스벨트(Svelte)와 같은 리액티브 프레임워크를 통합하기 위한 메타프레임워크로 유명하다. 알파인 역시 리액티브 프레임워크지만 매우 경량화된 설계 덕분에 아스트로와 함께 사용될 때는 통합에 따르는 일부 형식적인 요소를 피해간다.
아스트로는 서버 측 구성요소 정의에서 강력한 기능을 제공하고, 알파인의 깔끔한 구문은 상호작용과 API 호출을 통해 애플리케이션의 다양한 부분을 손쉽게 보강할 수 있게 해준다는 점에서 둘의 조합은 흥미로운 스택이다.
아스트로에는 공식 알파인 플러그인이 기본 포함되므로 자연스럽게 함께 사용할 수 있다. 아스트로에서 가져오는 정적 사이트 생성(SSG) 데이터로 알파인 구성요소를 움직이기 위해서는 약간의 작업이 필요하지만 크게 불편한 부분은 아니다. 주로 아스트로의 SSG에서 실행되면서 좀더 세련된 상호작용에는 알파인을 활용하는 앱을 구축하고자 한다면 둘은 좋은 조합이다.
필자는 아스트로-알파인 조합을 세 가지 개발 계층으로 분류하는데, 알파인에 대한 의존도를 기준으로 오름차순으로 정리하면 다음과 같다.
- - 계층 1 : 간단한 클라이언트 측 보강이 가미된 SSG
- - 계층 2 : 동적 SSR(서버 측 렌더링)과 클라이언트 측 보강
- - 계층 3 : API 호출을 사용한 클라이언트 측 렌더링
각 계층 간의 차이점은 예제를 통해 더 명확히 이해할 수 있을 것이다.
예제 애플리케이션 설정
이제부터 ‘Coast Mountain Adventures’라는 가상의 웹 애플리케이션을 구축할 것이다. 앱은 3개의 페이지로 구성되며, 이를 통해 3개의 개발 계층을 살펴본다.
- - 계층 1 : 소개(About) 페이지: 정적 콘텐츠가 포함된 간단한 아코디언 UI
- - 계층 2 : 장비(Gear) 매장: 아스트로가 생성하고 알파인이 필터링하는 지역 아웃도어 장비 매장 목록
- - 계층 3 : 내 모험(My adventures): 사용자 ID를 받아 사용자 데이터가 포함된 JSON 응답을 반환하는 API로, 알파인에 의해 클라이언트 측에서 렌더링된다.
먼저 아스트로를 설치한다. 그 다음 새 아스트로 애플리케이션을 생성하고 알파인 통합을 설치한다(참고로 필자는 쉽고 응답성이 높은 스타일을 위해 테일윈드(Tailwind) 플러그인을 설치했지만 CSS는 기사의 범위를 벗어나므로 다루지 않음.)
$ npm create astro@latest -- --template minimal $ npx astro add alpinejs $ npx astro add tailwind
아스트로의 CLI는 많은 정보를 제공하며 사용하기 쉽다.
예제 앱의 레이아웃
아스트로는 앱의 다양한 섹션을 스마트하게 패키징한다. 예제의 프레임(레이아웃)는 단순해서 제목과 링크 몇 개만으로 구성된다. 아스트로는 이를 번들로 묶어 배포하며, 이 과정에 자바스크립트는 사용되지 않는다.
---
import './src/styles/global.css';
---
Astro Alpine
Coast Mountain Adventures
몇 가지 스타일링과 CSS 가져오기 부분도 있지만 여기서는 생략한다. 주목할 부분은
소개 페이지
예제의 소개 페이지는 클라이언트 측 보강이 적용된 SSR이다. 페이지는 주 섹션 하나와 하위 섹션 2개, 총 3개의 섹션으로 구성된다. 여기서는 두 하위 섹션을 아코디언 패널로 만들지만 실제 환경이라면 굳이 콘텐츠를 숨길 필요가 없으니 이렇게 하지 않을 것이다. 아코디언 패널을 클릭해서 본문을 표시하거나 숨길 수 있다.
--- import Layout from '../layouts/Layout.astro'; ---About Us
We are a consortium of outdoor enthusiasts and locally owned gear shops and exchanges, dedicated to the truth that when nature and people come together, Good Things happen.
Our Mission {/* Add the icon span */} ↓ {/* Down arrow character */}
To connect people with nature and provide access to quality outdoor gear, experiences and curated resources.
Our Values ↓
- - Community
- - Sustainability
- - Adventure
이 섹션에는 아스트로 레이아웃을 사용한다는 점을 제외한 다른 아스트로의 특징적인 요소는 없다. HTML에 알파인 지시문이 곳곳에 첨가돼 있을 뿐이다. 여기 사용된 세 가지 유형의 지시문은 다음과 같다.
- -
x-data: 알파인 구성요소의 데이터 객체를 정의한다. - -
x-on:click: onclick 핸들러를 정의한다. - -
x-show: 조건에 따라 요소를 표시하거나 숨긴다.
아코디언에 필요한 것은 이게 전부이고, 여기에 화살표 아이콘을 위한 약간의 CSS만 추가하면 된다. 이제 페이지는 다음과 같이 표시된다.
Matthew Tyson |
알파인 작업에 대한 더 자세한 내용은 알파인 소개 기사에서 볼 수 있다.
장비 매장 페이지
장비 매장 페이지의 주 구성 요소는 서버 측 데이터와 클라이언트 측 필터링이다. 이 페이지에서 백엔드에 있는 데이터를 가져와 클라이언트가 필터링할 수 있는 방식으로 렌더링하고자 한다. 이를 위해서는 아스트로를 사용해 서버 측 데이터 렌더링을 처리하고, 이를 소비할 수 있는 알파인 구성요소를 만들어 동적인 UI 상호작용을 제공해야 한다.
먼저 페이지 자체부터 보자.
---
// src/pages/gear-shops.astro
import Layout from '../layouts/Layout.astro';
import GearShopList from '../components/GearShopList.astro';
const gearShops = [
{ name: "Adventure Outfitters", category: "Hiking" },
{ name: "Peak Performance Gear", category: "Climbing" },
{ name: "River Rat Rentals", category: "Kayaking" },
{ name: "The Trailhead", category: "Hiking" },
{ name: "Vertical Ventures", category: "Climbing" }
];
---
Local Gear Shops
GearShopList 구성요소에 대해서는 잠시 후에 살펴볼 것이다. 데이터는 gearShops 변수에 저장된다. 출처는 데이터베이스 또는 외부 API이거나 로컬 파일 시스템, 아스트로의 내장 콘텐츠 컬렉션일 수도 있다. 핵심은 이 데이터가 서버에서 생성된다는 것이다. 이제 해야 할 일은 이 데이터를 알파인이 사용할 수 있도록 클라이언트에서 라이브 JSON으로 제공하는 것이다.
첫 번째 단계는 GearShopList 구성요소의 shops 속성에 있는 데이터를 문자열로 변환하는 것이다. 이렇게 하면 아스트로는 브라우저로 뷰를 전송할 때 이를 직렬화할 수 있다. 즉, JSON.stringify(gearShops)가 HTML 문서에 인라인으로 삽입된다.
GearShopList는 다음과 같다.
---
const { shops, initialCount } = Astro.props; // Receive the 'shops' prop
if (!shops) {
throw new Error('GearShopList component requires a "shops" prop that is an array.');
}
---
- ()
다른 부분은 쉽게 이해되지만 x-data 속성인 에는 설명이 필요하다.
이 요소의 목적은 명확하다. filter 필드와 shops 필드가 있는 자바스크립트 객체다. shops 필드에는 앞서 서버에서 만든 라이브 자바스크립트 객체가 들어가며, 추가된 여러 특수 문자는 이를 구문으로 구현하기 위한 것이다. x-data 값을 이해하기 위한 열쇠는 “{``}” 구문이다. 두 개의 백틱이 중괄호 안에 들어간 형태인 이 구문은 빈 문자열로 해석된다.
이는 x-data 필드가 궁극적으로 shops 변수가 JSON으로 보간된 문자열 리터럴 객체가 되며, 알파인은 이것을 모두 소비해 구성요소를 움직이는 라이브 객체로 변환한다는 것을 의미한다. (깃허브에 아스트로-알파인 데모 앱이 있다. 데모 앱은 여기 사용된 shops 변수와 비슷하게 작동하는 카운터 구성요소를 사용하므로 앱의 이 부분이 어떻게 작동하는지 이해하는 데 도움이 될 것이다.)
x-data 속성 외에, select에는 선택 값을 filter 변수에 바인딩하는 x-model 속성이 있다. x-for는 shops를 반복 처리할 수 있게 해준다. 또한 기능적 의사결정을 사용해서 필터 조건과 일치하는 항목만 x-show를 이용해 화면에 표시한다.
장비 매장 페이지는 다음과 같다.

Matthew Tyson
모험 페이지
이 페이지는 API 호출을 통한 클라이언트 측 렌더링으로 구성되므로 알파인이 담당한다. 프리 렌더링을 비활성화해서 아스트로가 SSG를 수행하지 않도록 한다.
---
import Layout from '../layouts/Layout.astro';
export const prerender = false; // Important: Disable prerendering for this page
---
My Adventures
res.json())
.then(data => this.adventures = data)
.catch(error => console.error('Error fetching adventures:', error));
}
}" x-init="fetchAdventures()">
Loading adventures...
{/*... other adventure details... */}
이 페이지에서 알파인의 다재다능함이 잘 드러난다. x-data 속성이 data 속성(adventures)과 데이터를 불러오는 메서드(fetchAdventures())를 모두 포함하는 것을 볼 수 있다. 필자가 알파인에서 가장 좋아하는 부분으로, 깔끔하면서 강력하다.
구성요소 초기화에서 호출되는 x-init는 fetchAdventures() 함수를 호출하며, 이는 서버를 호출해 adventures를 채운다. 데이터, 동작, 뷰가 논리적으로 연결돼 작은 영역에 잘 담겨 있다.
이제 x-if를 사용해서 adventures.length에 따라 로딩 메시지 또는 실제 목록을 조건부로 렌더링한다.
adventures API
이 fetch 호출의 adventures JSON을 제공하기 위해서는 다음과 같은 백엔드 엔드포인트도 필요하다.
export async function GET() {
const adventures = [
{ id: 1, title: "Hiking Trip to Mount Hood", date: "2024-08-15" },
{ id: 2, title: "Kayaking Adventure on the Rogue River", date: "2024-09-22" },
//... more adventures
];
return new Response(JSON.stringify(adventures), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
});
}
전체적으로 알파인과 아스트로를 사용한 클라이언트 측 API 호출 방식은 매끄럽게 작동한다. 상황이 더 복잡해진다면 클라이언트 측 스토어를 사용하는 방법도 있다. 어쨌든 이 스택은 번거로움을 최소화하면서 필요한 작업을 수행한다.
모험 페이지는 다음과 같다.

Matthew Tyson
결론
잘 작동하면서 좋은 개발자 경험을 제공하는 툴은 소프트웨어 만들기를 즐기는 개발자에게 기쁨을 준다. 아스트로와 알파인 모두 그러한 특성을 갖고 있다. 두 가지를 함께 사용하는 경험 역시 만족스럽다. 유일한 난관인 SSR 과정에서 알파인에 데이터를 전달할 방법만 정리하면 된다.
dl-itworldkorea@foundryco.com
Matthew Tyson editor@itworld.co.kr
저작권자 Foundry & ITWorld, 무단 전재 및 재배포 금지



























































