
일반 컴포넌트와 동일하게 사용.
-> props.children도 가져올 수 있음!
-> 중첩 가능
컴포넌트의 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" />
| Directive | Description |
|---|---|
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()],
});
slot태그를 사용하여 astro컴포넌트 내에서,프레임워크 컴포넌트의 하위에 또다른 astro컴포넌트를 전달하는 것은 가능!
---
import MyReactComponent from '../components/MyReactComponent.jsx';
import MyAstroComponent from '../components/MyAstroComponent.astro';
---
<MyReactComponent>
<MyAstroComponent slot="name" />
</MyReactComponent>
vercel, netlify 등으로 배포할 때 공식 어댑터를 추가해주기
ex) @astrojs/netlify, @astrojs/vercel
서버에서 프로젝트를 실행하고 요청에 따라 HTML을 생성하여 클라이언트 측으로 보내줌
기본적으로 주문형 렌더링
-> 요청시 대부분 또는 전부를 서버 렌더링
-> 모든 개별 페이지 또는 엔드포인트는 사전 렌더링 선택 가능
---
export const prerender = true; //사전렌더링을 원하면 해당 항목 추가
---
<html>
<!-- 정적, 사전 렌더링된 페이지-->
</html>
기본적으로 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(), //이렇게 추가해주기
});
문서를 여러 개의 청크로 분할하여 전송 및 렌더링하는 기능
-> 더 빠른 페이지 로딩이 가능!
-> 쪼개기를 열심히 해야 하는 이유..!
---
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>
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>
---
import { getProduct } from '../api';
const product = await getProduct(Astro.params.id);
if (!product)
return new Response(null, {
status: 404,
statusText: '찾을 수 없음'
});
---
<html>
<!-- 페이지... -->
</html>
---
const cookie = Astro.request.headers.get('cookie'); //api의 헤더에 접근 가능
// ...
---
<html>
<!-- 페이지... -->
</html>
동적 경로와 일치하는 모든 요청에 응답
-> 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" },
});
}