lechuck.dev

Astro와 아일랜드 아키텍처

마지막 업데이트:

Astro

Astro는 빠른 웹 사이트를 쉽게 만들어주는 웹 프레임워크입니다. 빠른 웹 사이트를 만드는 간단한 비결은 서버에서 다운받는 전송량을 줄이면 됩니다.

Astro는 React, Vue 와 같은 자바스크립트 라이브러리를 사용하는 기존의 웹 사이트 구축 방법과는 다른 접근 방식을 취합니다. Astro는 전체 페이지를 정적 HTML로 렌더링하여 최종 빌드에서는 모든 자바스크립트가 기본적으로 제거됩니다.

물론 인터랙티브한 컴포넌트가 필요하다면 React, Svelte, Solid.JS, 웹 컴포넌트와 같은 원하는 라이브러리를 사용할 수 있습니다.

이 글에서는 2023년 1월에 릴리즈된 Astro v2 를 기준으로 Astro 의 여러가지 특징 중 하나인 아일랜드 아키텍처Island Architecture에 대해서 알아보도록 하겠습니다.

아일랜드 아키텍처

페이지 렌더링에 많은 자바스크립트를 로드하고 처리하면 성능이 저하될 수 밖에 없습니다. 대부분의 정적인 웹 사이트에서도 인터랙티브한 컴포넌트가 필요하기 때문에 어느 정도의 자바스크립트는 필요한 경우가 대부분입니다.

SSR

SSR의 핵심 원칙은 HTML이 서버에서 렌더링되며, 하이드레이션을 위해 필요한 자바스크립트가 같이 클라이언트에 같이 제공됩니다. 이러한 하이드레이션에는 비용이 들기 때문에 SSR 기반의 방법들은 하이드레이션 프로세스를 최적화하는데 중점을 두고 있습니다.

프로그레시브 하이드레이션

프로그레시브 하이드레이션은 중요한 컴포넌트를 먼저 하이드레이션하며, 나머지 컴포넌트는 점진적으로 스트리밍을 통해 하이드레이션됩니다. 하지만, 클라이언트에 제공되는 자바스크립트는 여전히 동일하게 유지됩니다.

아일랜드 아키텍처

아일랜드 아키텍처라는 용어는 Katie Sylor-Miller 와 Jason Miller 에 의해 널리 알려지기 시작했습니다.

이는 정적 HTML 위에 독립적으로 전달할 수 있는 인터랙티브한 “아일랜드”를 통해 클라이언트에 전달되는 자바스크립트의 양을 줄이는 것을 목표로 합니다. 아일랜드는 컴포넌트 기반 아키텍처로, 페이지를 여러 개의 아일랜드로 영역으로 구분합니다. 페이지의 정적 영역은 순수 HTML로 구성되며 하이드레이션이 필요하지 않습니다. 동적 영역은 렌더링 후 자체적으로 하이드레이션이 가능한 HTML와 자바스크립의 조합이 됩니다.

동적 컴포넌트 아일랜드

대부분의 페이지는 정적 컨텐츠와 동적 컨텐츠로 이루어집니다. 일반적으로 페이지에는 정적 컨텐츠와, 분리된 영역의 인터랙티브한 동적 컴포넌트로 구성됩니다. 예를 들면:

정적 컨텐츠는 상태를 가지지 않고, 이벤트를 실행하지 않기 때문에 하이드레이션이 필요없습니다. 렌더링 후에는 동적 컨텐츠에 이벤트 핸들러를 연결하고, 가상 DOM을 사용해 DOM 을 생성해야 합니다. 이러한 작업들은 클라이언트로 전송되는 자바스크립트에 의해 이루어집니다.

아일랜드 아키텍처에서는 서버에서 정적 컨텐츠를 렌더링하며, 렌더링된 HTML에는 동적 컨텐츠를 위한 플레이스홀더가 포함됩니다. 동적 컨텐츠 플레이스홀더에는 독립된 컴포넌트들이 포함되며, 서버에서 렌더링된 출력물을 클라이언트에서 하이드레이션하는데 필요한 최소한의 자바스크립트를 포함합니다.

프로그레시브 하이드레이션에서 페이지는 개별 컴포넌트의 스케줄링과 하이드레이션을 제어합니다. 각 컴포넌트는 페이지 내의 다른 컴포넌트와 독립적으로 비동기 실행되는 하이드레이션 스크립트가 있으며, 한 컴포넌트에 성능 문제가 발생하더라도 다른 컴포넌트에는 영향을 미치지 않아야 합니다.

아일랜드의 장점

Astro 아일랜드 예제

여기에서는 solid.js 를 연동해서 astro 아일랜드 예제를 만들어 보겠습니다.

solid.js 연동

solid.js 를 연동하기 위해서는 내장된 astro add CLI 도구를 사용합니다.

$ pnpm astro add solid

실행하게 되면 다음과 같은 파일들이 업데이트 됩니다.

astro.config.mjs

// ...
import solidJs from "@astrojs/solid-js";

export default defineConfig({
  // ...
  integrations: [solidJs(), /** ... */],
});

package.json

tsconfig.json

{
  "compilerOptions": {
    "jsx": "preserve",
    "jsxImportSource": "solid-js"
  }
}

solid.js 컴포넌트 임포트

InputCounter라는 solid.js 컴포넌트를 다음과 같이 작성합니다.

import { createSignal } from "solid-js";

export default function InputCounter() {
  const [count, setCount] = createSignal(0);

  const onDecrease = () => setCount(count() - 1);
  const onIncrease = () => setCount(count() + 1);
  const onInputChange = (s: string) => setCount(Number.parseInt(s));

  return (
    <div>
      <button onClick={onDecrease}>-</button>
      <input
        type="text"
        value={count()}
        onChange={(e) => onInputChange(e.target.value)}
      />
      <button onClick={onIncrease}>+</button>
    </div>
  );
}

다음과 같이 작성하게 되면 solid.js 컴포넌트를 임포트했더라도, 결과물은 자바스크립트를 포함하지 않는 순수 HTML 만 생성됩니다.

---
// 정적 solid.js 컴포넌트
import InputCounter from "../../components/solid/InputCounter";

---
<!-- 자바스크립트를 사용하지 않는 100% 순수 HTML 생성 -->
<InputCounter />

하지만 클라이언트 사이드에서 인터랙티브한 UI를 필요로 하는 경우, client:load 지시자를 사용해 SPA 자바스크립트 애플리케이션처럼 동적 아일랜드를 생성하도록 지시할 수 있습니다.

---
// 동적 solid.js 컴포넌트
import InputCounter from "../../components/solid/InputCounter";
---
<!-- 이 컴포넌트는 인터랙티브하게 동작하며,
     페이지의 나머지 부분은 자바스크립트를 사용하지 않는 정적 웹사이트가 됩니다. -->
<InputCounter client:load />

참고 문서