자중하는 시간을 가지고 공식 문서에 기반한 팩트 위주로 글을 쓰도록 하겠다.
Astro는 좁게 말하면 멀티 페이지형 SSG(Stataic Site Generator) 프레임워크다.
그리고 넓게 말하면, 그들이 주장하는 아일랜드 아키텍쳐형 사이트 생성기다.
물론 SSR 기능도 지원하지만, 기본적으로 멀티 페이지 SSG라는 것만 알아두자. 억지로 이 프레임워크로 SPA 앱 만들다 잡치지 말고.
Astro 에 대해 들어보거나 체험해 보았다면, react, vue, svelte 등 주요 프레임워크의 통합 기능은 매우 강력한 것으로 알려져 있다. 혹은 나처럼 경험해 보기도 했을 것이다.
기본적으로, 이 react, vue, svelte 는 자체에서 제공하는 hydration
, 즉, 페이지 결과물을 받아들이는 방식을 사용한다.
따라서, 이론적으로는 이 하이드레이션을 지원하는 프론트엔드 기술이라면 모두 다 지원한다고 보면 되는데,
아직까지는 리액트, 뷰, 스벨트, 솔리드, 프리액트 등의 인지도 있는 기술들을 위주로 지원하고 있고, 더 넓힌다고는 하지만 이는 커뮤니티의 지원이 필요한 부분이라 하겠다.
어쨌든, Astro의 클라이언트 렌더링을 지금부터 정리하도록 하겠다.
기본적으로, 없으면, Astro는 컴포넌트 기술로부터 제공받은 하이드레이션을 통해 산출된 HTML, CSS 등의 결과를 렌더링하고, 여기서 끝난다. 그렇기 때문에, 리액트의 서버 컴포넌트처럼, 상태관리를 사용할 수 없고(정확히 말하면 상태관리 정의만 가능할 뿐, 먹통 그 자체), 동적 기능조차 제공되지 않은 정적 결과물만 렌더링된다. 따라서 동적 화면 변경이 발생하는 경우가 조금이라도 있다면, 컴포넌트를 불러올 때 아래 특성 중 하나를 반드시 적용해야 한다.
이 특성이 적용된 컴포넌트는 가장 우선적으로 불러와지며, 문서 불러오자마자 리액트, 뷰, 스벨트 등의 외부 컴포넌트의 초기화를 시작한다고 보면 된다. 예를 들어 첫 페이지에 띄울 인트로성 동작 화면이 있으면 이 특성이 적합하다고 보면 된다.
장점: 불러오자마자 가장 우선적으로 불러와지기 때문에 빠르게(성능과 무관) 컴포넌트를 눈에 보이게 할 수 있다.
단점: 이런 대상이 많을 수록... 알지?
<MyButton client:load />
이 특성이 적용된 컴포넌트는 우선 순위가 client:load
다음이라고 보면 된다. 이녀석이 렌더링 되는 시점이 requestIdleCallback
이벤트인데, 브라우저가 idle 상태라고 판단하면 호출하는 이벤트다. 이 이벤트를 간단히 말하면, 애니메이션이라든가 여러 작업에 의한 이벤트 및 브라우저 동작을 마치고 쉬는 시간에 작동하는 이벤트라고 알면 되기는 한데... 사실 솔직히 그 시점에 대해 나도 감을 잡기가 어렵고, 현재 안정화된 기술은 아니기에 사파리 등의 일부 브라우저에서 적용할 수 없으며, 브라우저에서 적용할 수 없는 경우, client:load
특성과 똑같이 적용된다.
<MyStatus client:idle />
이 특성이 적용된 컴포넌트는 불러와도 끄떡 안하고, 유휴 상태에도 끄떡 없고, 사용자가 해당 컴포넌트가 있는 위치까지 스크롤해서 눈에 보여야 렌더링을 시작하는 특성이다. 기술적으로 IntersectionObserver
클래스를 사용하는데, 아는 사람은 알 거다. 특정 요소가 시야 안에 있는지 밖에 있는지 알려주는 참으로 유용한 기능이라 하겠다. 이녀석은 이런 특징을 가지고 있기 때문에 렌더링 우선 순위는 낮다. 주로 리소스 쳐묵하는 이미지나, 그 이미지를 모아둔 캐러셀 등의 컴포넌트를 브라우저 시야 밖에다 놨을 때 적합하다고 하겠다.
<MyGallery client:visible />
여기서부터는 값을 설정할 수 있는 렌더링 특성이다. 값에다가는 반응형 웹에 질리도록 써먹는 그 CSS의 넓이 조건을 넣으면 되겠다. 따라서 한 번이라도 반응성 조건에 만족하면 렌더링을 시작한다는 얘기가 되겠다. 이녀석 또한 나중에 렌더링되는 특성을 가졌기 때문에 우선순위는 낮으며, 주로 모바일과 PC를 따로 구분하여 렌더링하고자 하는 시나리오에 매우 적합하다 하겠다. 특히 PC는 한눈에 잘 보이지만 모바일은 보기가 껄끄러운 데이터그리드 같은 것들.
<SidebarToggle client:media="(max-width: 50em)" /> <!-- 브라우저 가용 화면 넓이가 50자 이하이면 렌더링 시작! -->
이 특성은, 아예 하이드레이션을 쓰지 않겠다는 의도로 사용할 때 넣으면 된다. Next.js 주요 프론트엔드 기술에서 하이드레이션이 요구되는 풀스택 등의 개발 환경에서 몇몇 UI 컴포넌트를 갖다 쓸 경우 하이드레이션을 고려하지 않은 UI 컴포넌트가 꼭 하나씩은 있어서 골머리를 앓기 마련인데, 이런 류의 컴포넌트를 불러와야 할 때, 이녀석을 쓴다. 작동 방식은 client:load
와 동일하며, 값을 넣어야 하는데, Astro 는 렌더링을 아예 해당 컴포넌트 기술에 위임하는 방식이라 Astro가 인지를 할 수 없다고 하니, 문자열로 기술명을 명시해야 한다는 단점이 있다.
<SomeReactComponent client:only="react" />
<SomePreactComponent client:only="preact" />
<SomeSvelteComponent client:only="svelte" />
<SomeVueComponent client:only="vue" />
<SomeSolidComponent client:only="solid-js" />
이런 식으로.
Astro 2.6 부터는 사용자가 직접 렌더링 시점을 지정할 수 있는 API를 제공하기 시작했다. 나도 바라던 바다. 역시 Astro는 내 최애의 아이(프레임워크)라 하겠다. 썸네일이 씹덕인 이유가 바로 이녀석 때문이고, 이녀석이 주인공이며, 이녀석을 소개하기 위해 글을 쓴 것이다.
정의하는 방법은 아래와 같다. 공식 문서에서 제공한 클릭 시 렌더링 예제를 둔다.
addClientDirective
API가 추가되었다. 따라서 아래 구문을 astro.config.js
에 추가하면 된다.
// Create a `client:click` directive
addClientDirective({
name: 'click',
entrypoint: 'astro-click-directive/click.js'
})
이 때, entrypoint가 스크립트 경로인데, 이는 esbuild
가 빌드할 때 Astro가 렌더링을 위한 빌드 시 참고하도록 하기 위해서이다. 쉽게 말하면, 빌드 시점에 네가 렌더링 전략을 직접 정의하고 싶을 때 추가하는 파일이라 생각하면 된다.
렌더링 로직 함수를 export default
로 선언하여 Astro가 default 로 불러와 읽을 수 있도록 로직 함수를 구성한다.
// astro-click-directive/click.js
/**
* Hydrate on first click on the window
* @type {import('astro').ClientDirective}
*/
export default (load, opts, el) => {
window.addEventListener('click', async () => {
const hydrate = await load()
await hydrate()
}, { once: true }) // 한 번만 이벤트를 실행하여 또 불러오는 대참사 없도록 주의한다.
}
타입스크립트 사용 시, 특성의 자동완성 및 도움을 위하여 아래와 같이 인터페이스를 지정하면 매우 좋다. 공식이 그렇게 말했어.
// astro-click-directive/index.d.ts
declare module 'astro' {
interface AstroClientDirectives {
'client:click'?: boolean
}
}
별거 있나? 이제 필요한 곳에 써야지!
<MyTutorial client:click />
여기서부턴 내 경험담이 좀 들어갈텐데, 렌더링 방식을 사용할 경우, 실제 페이지에 소스보기를 통해 해당 컴포넌트 위치를 보면 <astro-island>
라는 생소한 태그와 함께 괴랄한 스크립트와 어느정도 렌더링 된 마크업, CSS 등을 볼 수 있다. 이녀석의 용도는 간단하게 "여기서부터는 클라이언트 렌더링 시발점" 이라고 간단하게 생각하면 된다. 이 방식을 사용하여 비슷하게 컴포넌트 렌더링 범위를 관리하는 react 의 사례로는 Million.js 가 있다.
<astro-island>
태그는 display:contents
CSS 가 적용되어 있어 왠만하면 이 태그 상위의 태그를 CSS 부모로 인지하도록 되어 있지만, CSS 에서 display:contents
속성을 지원하지 않거나, 혹은 해당 속성을 지원하지 않는 속성을 지난 속성이 있거나, JS에서 부모를 가져오거나 참고하는 등의 경우는 부모를 오인할 수 있는 등의 시나리오에서 렌더링이 깨지거나 올바르게 표시되지 않을 수 있으므로, 직접 렌더링 결과를 확인하면서 대비를 하는 것이 좋다.
내가 주는 현실적인 조언은, 왠만하면 아일렌드 아키텍쳐 답게 부모가 무엇이든 렌더링 가능하도록 독립적으로 표시할 수 있도록 대응하는 것이 좋다. 나도 회사 홈페이지 만들면서 이거 때문에 캐러셀 표현하다가 페이지에 컴포넌트로 넣었는데... 이놈 하나 때문에 씹창난 레이아웃 고치는데 하루죙일 고생한 적 있다.
아, 참고로 Million.js 썼을때도 비슷한 고생 한 적 있다. 하아... 망할 유동 높이 스크롤...
아직 뾰족한 렌더링 전략을 찾지 못했지만, 혹시라도 찾으면 난 이 글을 적극적으로 활용하도록 하겠다.
어? Astro 안쓴다고? 한번 써봐. 네 블로그라도 상관 없어. 츄라이~ 츄라이!
마침 여기 벨로그만 봐도 츄라이 당한 애들 있다니까!
난 회사 홈페이지 이걸로 작성했다고. 확실히 풍부하고, 기존 관리자에서 쓰던 React 컴포넌트와의 궁합도 끝내줘!
아 Astro의 단점 하나 있다. 이녀석은 대놓고 IE 미지원이다. MS도 완전히 버린 IE 아직도 신경써야 하는 한국이 참... 슬프다.
끗.
아직도 IE를 신경써야 한다는 곳이 있다는 것이 슬프네요.ㅠㅠ Astro 정말 좋다고 생각합니다. 핵심 기능을 알차게 깔끔하게 정리해주셔서 감사합니다. 이제 Directive가 커스텀이 되는거 알아 갑니다! :)