통합

MM·2024년 4월 29일

AstroDeepDive

목록 보기
3/7
post-thumbnail

프레임워크 컴포넌트

인터랙티브 컴포넌트 하이드레이션하기

일반 컴포넌트와 동일하게 사용.
-> props.children도 가져올 수 있음!
-> 중첩 가능

client:* 지시어

컴포넌트의 js가 브라우저로 전송되는 시기를 결정하는 속성
client:only외에는 사전에 서버에서 렌더링되어 정적 HTML을 생성 + JS는 이후 지시어에 따라 브라우저로 전송됨

---
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="react" />

DirectiveDescription
client:load페이지 로드 시 JavaScript를 즉시 로드
client:idle페이지 초기 로드 완료 후 요청 콜백이 들어오면 JavaScript를 로드
client:visible컴포넌트가 사용자 뷰포트에 들어가면 JavaScript를 로드
-> 내부적으로 intersect observer를 사용하여 가시성 추적
client:visible={{rootMargin: "100px" }}컴포넌트에 해당 마진을 포함한 영역이 뷰포트에 들어가면 JavaScript를 로드
-> CLS가 줄어들고 컴포넌트가 더 빨리 대화형으로 만들어져 페이지의 안정성과 응답성 향상
client:media특정 CSS 미디어 쿼리가 충족되면 JavaScript를 로드
-> CSS에서 미디어 쿼리를 사용하고 있다면 client:visible만 사용
client:only={Framwork}서버 HTML 렌더링을 건너뛰고 클라이언트에서만 렌더링

CLS(레이아웃 변경)

레이아웃의 시각적 안정성을 측정하는 항목


사용자 정의 지시어

window 등 해당하는 컴포넌트에 이벤트를 추가하는 화살표 함수를 만들고,
해당 함수를 addClientDirective에 넣어 astro설정파일에 추가하면 됨.

//창 클릭시 수화되는 지시어 만들기
//click.js
export default (load, opts, el) => {
  window.addEventListener(
    'click',
    async () => {
      const hydrate = await load();
      await hydrate();
    },
    { once: true } //일회성 설정
  );
};


//register.js
export default () => ({
  name: 'client:click',
  hooks: {
    'astro:config:setup': ({ addClientDirective }) => {
      addClientDirective({
        name: 'click',
        entrypoint: './astro-click-directive/click.js',
      });
    },
  },
});



//astro.config.mjs
import { defineConfig } from 'astro/config';
import clickDirective from './astro-click-directive/register.js';

export default defineConfig({
  integrations: [clickDirective()],
});


프레임워크 컴포넌트 중 Astro 컴포넌트 사용하기

slot태그를 사용하여 astro컴포넌트 내에서,프레임워크 컴포넌트의 하위에 또다른 astro컴포넌트를 전달하는 것은 가능!

---
import MyReactComponent from  '../components/MyReactComponent.jsx';
import MyAstroComponent from '../components/MyAstroComponent.astro';
---
<MyReactComponent>
  <MyAstroComponent slot="name" />
</MyReactComponent>



SSR

vercel, netlify 등으로 배포할 때 공식 어댑터를 추가해주기
ex) @astrojs/netlify, @astrojs/vercel

어댑터

서버에서 프로젝트를 실행하고 요청에 따라 HTML을 생성하여 클라이언트 측으로 보내줌

  • API 엔드포인트
    • 중요한 데이터를 클라이언트에게 숨기면서 API처리 가능
    • 데이터베이스 액세스, 인증, 권한 부여와 같은 작업 가능
  • 보호된 페이지
    • 서버에서 사용자 액세스를 처리
    • 사용자 권한에 따라 페이지 액세스 제한
  • 자주 변경되는 콘텐츠
    • 사이트를 다시 정적으로 빌드하지 않고도 개별 페이지 생성 가능
    • 페이지 내용이 자주 업데이트되는 경우 유용

주문형 서버 렌더링 활성화

주문형 렌더링 출력 모드

output: 'server'

기본적으로 주문형 렌더링
-> 요청시 대부분 또는 전부를 서버 렌더링
-> 모든 개별 페이지 또는 엔드포인트는 사전 렌더링 선택 가능

---
export const prerender = true; //사전렌더링을 원하면 해당 항목 추가
---
<html>
  <!-- 정적, 사전 렌더링된 페이지-->
</html>

output: 'hybrid'

기본적으로 HTML로 사전 렌더링
-> 대부분의 사이트가 정적
-> 모든 개별 페이지 또는 엔드포인트는 사전 렌더링 거부 가능

export const prerender = false; //사전렌더링 거부를 원하면 해당 항목 추가

export async function GET() {
  let number = Math.random();
  return new Response(
    JSON.stringify({
      number,
      message: `임의의 숫자: ${number}`,
    }),
  );
}

어댑터 추가

주문형 렌더링 출력 모드로 프로젝트를 배포하려면 요청을 받을 서버 런타임 필요
-> 어댑터 추가 필요!

//astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel(), //이렇게 추가해주기
});


주문형 렌더링 기능

HTML 스트리밍

문서를 여러 개의 청크로 분할하여 전송 및 렌더링하는 기능
-> 더 빠른 페이지 로딩이 가능!
-> 쪼개기를 열심히 해야 하는 이유..!

스트리밍 전

---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<html>
  <head>
    <title>A name and a fact</title>
  </head>
  <body>
    <h2>A name</h2>
    <p>{randomPerson.name.first}</p>
    <h2>A fact</h2>
    <p>{factData.fact}</p>
  </body>
</html>

스트리밍용으로 분할

Astro는 제목과 같은 일부 HTML을 먼저 렌더링한 다음 데이터가 준비되면 해당 단락을 렌더링한다.
-> api를 사용하는 컴포넌트 외의 정적인 부분(head, body 태그 등)은 더 빨리 사용자가 사용할 수 있다!

//RandomName.asto
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
---
<p>{randomPerson.name.first}</p>


//RandomFact.astro
---
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<p>{factData.fact}</p>


//index.astro
---
import RandomName from '../components/RandomName.astro';
import RandomFact from '../components/RandomFact.astro';
---
<html>
  <head>
    <title>A name and a fact</title>
  </head>
  <body>
    <h2>A name</h2>
    <RandomName />
    <h2>A fact</h2>
    <RandomFact />
  </body>
</html>

cookies

astro은 ssr에서 쿠키 사용이 가능!

---
let counter = 0

if (Astro.cookies.has("counter")) {
  const cookie = Astro.cookies.get("counter")
  counter = cookie.number() + 1
}

Astro.cookies.set("counter",counter)
---
<html>
  <h1>카운터 = {counter}</h1>
</html>

Response

---
import { getProduct } from '../api';

const product = await getProduct(Astro.params.id);

if (!product) 
  return new Response(null, {
    status: 404,
    statusText: '찾을 수 없음'
  });

---
<html>
  <!-- 페이지... -->
</html>

Request

---
const cookie = Astro.request.headers.get('cookie'); //api의 헤더에 접근 가능
// ...
---
<html>
  <!-- 페이지... -->
</html>

서버 엔드포인트 (api 라우트)

동적 경로와 일치하는 모든 요청에 응답
-> get, post, delete, all 사용가능

//src/pages/[id].json.js
import { getProduct } from '../db';

export async function GET({ params, request }) {

  const id = params.id;
  const product = await getProduct(id);

  if (!product || request.headers.get("Content-Type") === "application/json") 
    return new Response(null, {
      status: 404,
      statusText: 'Not found'
    });

  else return new Response(
    JSON.stringify(product), {
      status: 200,
      headers: {
        "Content-Type": "application/json"
      }
    }
  );
}


//이미지인 경우는 버퍼도 함께 필요
//src/pages.astro-logo.png.ts
export async function GET({ params, request }) {
  const response = await fetch("https://docs.astro.build/assets/full-logo-light.png");
  const buffer = Buffer.from(await response.arrayBuffer());
  return new Response(buffer, {
    headers: { "Content-Type": "image/png" },
  });
}
profile
중요한 건 꺾여도 그냥 하는 마음

0개의 댓글