컨텐츠로 건너뛰기

Astro 어댑터 API

Astro는 SSR (서버 측 렌더링)을 위해 모든 클라우드 제공업체에 쉽게 배포할 수 있도록 설계되었습니다. 이 기능은 통합으로 제공되는 어댑터 를 통해 사용할 수 있습니다. 기존 어댑터를 사용하는 방법을 알아보려면 SSR 가이드를 참조하세요.

어댑터는 서버 측 렌더링을 위한 엔트리포인트 제공하는 특별한 종류의 통합입니다. 어댑터는 두 가지 작업을 수행합니다.

  • 요청 처리를 위한 호스트별 API를 구현합니다.
  • 호스트 규칙에 따라 빌드를 구성합니다.

어댑터는 통합이며 통합이 수행할 수 있는 모든 작업을 수행할 수 있습니다.

어댑터는 반드시 다음과 같이 astro:config:done 후크에서 setAdapter API를 호출해야 합니다.

my-adapter.mjs
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js',
supportedAstroFeatures: {
staticOutput: 'stable'
}
});
},
},
};
}

setAdapter에 전달된 객체는 다음과 같이 정의됩니다.

interface AstroAdapter {
name: string;
serverEntrypoint?: string;
exports?: string[];
adapterFeatures: AstroAdapterFeatures;
supportedAstroFeatures: AstroFeatureMap;
}
export interface AstroAdapterFeatures {
/**
* Astro 미들웨어와 통신할 에지 기능을 생성합니다.
*/
edgeMiddleware: boolean;
/**
* SSR 전용입니다. 각 경로는 자체 함수/파일이 됩니다.
*/
functionPerRoute: boolean;
}
export type SupportsKind = 'unsupported' | 'stable' | 'experimental' | 'deprecated';
export type AstroFeatureMap = {
/**
* 어댑터는 정적 페이지를 제공할 수 있습니다.
*/
staticOutput?: SupportsKind;
/**
* 어댑터는 정적이거나 서버를 통해 렌더링되는 페이지를 제공할 수 있습니다.
*/
hybridOutput?: SupportsKind;
/**
* 어댑터는 SSR 페이지를 제공할 수 있습니다.
*/
serverOutput?: SupportsKind;
/**
* 어댑터는 정적 자산을 내보낼 수 있습니다.
*/
assets?: AstroAssetsFeature;
};
export interface AstroAssetsFeature {
supportKind?: SupportsKind;
/**
* 이 어댑터가 'sharp' 라이브러리와 호환되는 환경에 파일을 배포하는지 여부
*/
isSharpCompatible?: boolean;
/**
* 이 어댑터가 'squoosh' 라이브러리와 호환되는 환경에 파일을 배포하는지 여부
*/
isSquooshCompatible?: boolean;
}

속성은 다음과 같습니다.

  • name: 로깅에 사용되는 어댑터의 고유 이름입니다.
  • serverEntrypoint: 서버 측 렌더링의 엔트리포인트입니다.
  • exports: createExports와 함께 사용되는 경우 명명된 내보내기 배열입니다 (아래 설명 참조).
  • adapterFeatures: 어댑터에서 지원해야 하는 특정 기능을 활성화하는 객체입니다. 이러한 기능은 빌드된 출력을 변경하며 어댑터는 다양한 출력을 처리하기 위해 적절한 로직을 구현해야 합니다.
  • supportedAstroFeatures: Astro 내장 기능 맵입니다. 이를 통해 Astro는 어댑터가 지원할 수 없거나 지원하지 않는 기능을 판단하여 적절한 오류 메시지를 제공할 수 있습니다.

Astro의 어댑터 API는 모든 유형의 호스트와 작동하려고 시도하며 호스트 API를 준수하는 유연한 방법을 제공합니다.

일부 서버리스 호스트는 handler와 같은 함수를 내보낼 것을 기대합니다.

export function handler(event, context) {
// ...
}

어댑터 API를 사용하면 serverEntrypointcreateExports를 구현하여 이를 달성할 수 있습니다.

import { App } from 'astro/app';
export function createExports(manifest) {
const app = new App(manifest);
const handler = (event, context) => {
// ...
};
return { handler };
}

그런 다음 setAdapter를 호출하는 통합에서 exports에 이 이름을 제공합니다.

my-adapter.mjs
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js',
exports: ['handler'],
});
},
},
};
}

일부 호스트는 포트를 수신하여 서버를 직접 시작하기를 기대합니다. 이러한 유형의 호스트의 경우 어댑터 API를 사용하면 번들 스크립트가 실행될 때 호출되는 start 함수를 내보낼 수 있습니다.

import { App } from 'astro/app';
export function start(manifest) {
const app = new App(manifest);
addEventListener('fetch', event => {
// ...
});
}

이 모듈은 astro build를 통해 사전 빌드된 페이지를 렌더링하는 데 사용됩니다. Astro는 표준 RequestResponse 객체를 사용합니다. 요청/응답에 대해 다른 API를 사용하는 호스트는 어댑터에서 이러한 유형으로 변환해야 합니다.

import { App } from 'astro/app';
import http from 'http';
export function start(manifest) {
const app = new App(manifest);
addEventListener('fetch', event => {
event.respondWith(
app.render(event.request)
);
});
}

다음과 같은 메서드가 제공됩니다.

app.render(request: Request, options?: RenderOptions)
섹션 제목: app.render(request: Request, options?: RenderOptions)

이 메서드는 요청과 일치하는 Astro 페이지를 호출하고 렌더링한 후 Response 객체에 Promise를 반환합니다. 이는 페이지를 렌더링하지 않는 API 경로에도 적용됩니다.

const response = await app.render(request);

app.render() 메서드는 필수 request 인수와 addCookieHeader, clientAddress, locals, routeData에 대한 선택적 RenderOptions 객체를 허용합니다.

Astro.cookie.set()이 작성한 모든 쿠키를 응답 헤더에 자동으로 추가할지 여부입니다.

true로 설정하면 응답의 Set-Cookie 헤더에 쉼표로 구분된 키-값 쌍으로 추가됩니다. 표준 response.headers.getSetCookie() API를 사용하여 개별적으로 읽을 수 있습니다. false (기본값)로 설정하면 App.getSetCookieFromResponse(response)에서만 쿠키를 사용할 수 있습니다.

const response = await app.render(request, { addCookieHeader: true });

페이지에서는 Astro.clientAddress로, API 경로 및 미들웨어에서는 ctx.clientAddress로 사용할 수 있는 클라이언트 IP 주소입니다.

아래 예시에서는 x-forwarded-for 헤더를 읽고 이를 clientAddress로 전달합니다. 이 값은 사용자가 Astro.clientAddress로 사용할 수 있게 됩니다.

const clientAddress = request.headers.get("x-forwarded-for");
const response = await app.render(request, { clientAddress });

요청 수명 주기 동안 정보를 저장하고 액세스하는 데 사용되는 context.locals 객체입니다.

아래 예시에서는 x-private-header라는 헤더를 읽고 이를 객체로 구문 분석한 후 locals에 전달하려고 시도합니다. 그런 다음 미들웨어 함수에 전달될 수 있습니다.

const privateHeader = request.headers.get("x-private-header");
let locals = {};
try {
if (privateHeader) {
locals = JSON.parse(privateHeader);
}
} finally {
const response = await app.render(request, { locals });
}

렌더링할 경로를 이미 알고 있는 경우 routeData에 대한 값을 제공하세요. 그렇게 하면 렌더링할 경로를 결정하기 위해 app.match에 대한 내부 호출을 우회하게 됩니다.

const routeData = app.match(request);
if (routeData) {
return app.render(request, { routeData });
} else {
/* 어댑터별 404 응답 */
return new Response(..., { status: 404 });
}

이 메서드는 요청이 Astro 앱의 라우팅 규칙과 일치하는지 확인하는 데 사용됩니다.

if(app.match(request)) {
const response = await app.render(request);
}

Astro가 404.astro 파일을 제공하면 404를 처리하기 때문에 일반적으로 .match를 사용하지 않고 app.render(request)를 호출할 수 있습니다. 404를 다른 방식으로 처리하려면 app.match(request)를 사용하세요.

astro add를 통한 설치 허용

섹션 제목: astro add를 통한 설치 허용

astro add 명령을 사용하면 사용자가 프로젝트에 통합 및 어댑터를 쉽게 추가할 수 있습니다. 이 도구를 사용하여 여러분의 어댑터를 설치하려면 package.json 파일의 keywords 필드에 astro-adapter를 추가하세요:

{
"name": "example",
"keywords": ["astro-adapter"],
}

어댑터를 npm에 게시한 후, astro add example을 실행하면 package.json 파일에 지정된 피어 종속성과 함께 패키지가 설치됩니다. 또한 사용자에게 프로젝트 구성을 수동으로 업데이트하도록 지시할 것입니다.

Added in: astro@3.0.0

Astro 기능은 어댑터가 Astro에 기능을 지원할 수 있는지 여부와 어댑터의 지원 수준을 알려주는 방법입니다.

이러한 속성을 사용할 때 Astro는 다음을 수행합니다.

  • 특정 검증을 실행합니다.
  • 로그에 대한 상황에 맞는 정보를 내보냅니다.

이러한 작업은 지원되거나 지원되지 않는 기능, 지원 수준 및 사용자가 사용하는 구성을 기반으로 실행됩니다.

다음 구성은 Astro에 이 어댑터가 자산에 대한 실험적 지원을 제공하지만, 어댑터가 내장 서비스 Sharp 및 Squoosh와 호환되지 않음을 알려줍니다.

my-adapter.mjs
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js',
supportedAstroFeatures: {
assets: {
supportKind: "experimental",
isSharpCompatible: false,
isSquooshCompatible: false
}
}
});
},
},
};
}

Astro는 터미널에 경고를 기록합니다.

[@matthewp/my-adapter] The feature is experimental and subject to issues or changes.

자산에 사용되는 서비스가 어댑터와 호환되지 않으면 오류가 발생합니다.

[@matthewp/my-adapter] The currently selected adapter `@matthewp/my-adapter` is not compatible with the service "Sharp". Your project will NOT be able to build.

내보낸 파일의 출력을 변경하는 기능 세트입니다. 어댑터가 이러한 기능을 선택하면 특정 후크에서 추가 정보를 얻게 됩니다.

SSR만을 사용하는 경우에만 활성화되는 기능입니다. 기본적으로 Astro는 각 요청마다 렌더링된 페이지를 내보내는 역할을 하는 단일 entry.mjs 파일을 내보냅니다.

functionPerRoutetrue인 경우, Astro는 대신 프로젝트에 정의된 각 경로에 대해 별도의 파일을 생성합니다.

내보낸 각 파일은 한 페이지만 렌더링합니다. 페이지는 dist/pages/ 디렉터리 (또는 outDir에 지정된 디렉터리의 /pages/ 디렉터리 아래)에 생성됩니다. 내보낸 파일은 src/pages/ 디렉터리의 동일한 파일 경로를 유지합니다.

빌드의 pages/ 디렉터리에 있는 파일은 src/pages/에 있는 페이지 파일의 디렉터리 구조를 미러링합니다. 예를 들면 다음과 같습니다.

  • Directorydist/
    • Directorypages/
      • Directoryblog/
        • entry._slug_.astro.mjs
        • entry.about.astro.mjs
      • entry.index.astro.mjs

어댑터에 true를 전달하여 기능을 활성화합니다.

my-adapter.mjs
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js',
adapterFeatures: {
functionPerRoute: true
}
});
},
},
};
}

그런 다음 astro:build:ssr 후크를 사용하면 빌드 후 내보낸 실제 파일에 페이지 경로를 매핑하는 entryPoints 객체가 제공됩니다.

my-adapter.mjs
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js',
adapterFeatures: {
functionPerRoute: true
}
});
},
'astro:build:ssr': ({ entryPoints }) => {
for (const [route, entryFile] of entryPoints) {
// route 및 entryFile로 작업을 시작하세요.
}
}
},
};
}

서버리스 환경에서 functionPerRoute: true를 설정하면 각 경로에 대한 JavaScript 파일 (핸들러)이 생성됩니다. 핸들러는 호스팅 플랫폼 (lambda, function, page, 등)에 따라 다른 이름을 가질 수 있습니다.

이러한 각 경로는 핸들러가 실행될 때 콜드 스타트가 발생하여 약간의 지연이 발생할 수 있습니다. 이 지연은 다양한 요인의 영향을 받습니다.

functionPerRoute: false를 사용하면 모든 경로 렌더링을 담당하는 단일 핸들러만 존재합니다. 이 핸들러가 처음 트리거되면 콜드 스타트가 발생합니다. 그러면 다른 모든 경로가 지연 없이 작동해야 합니다. 그러나 functionPerRoute: true가 제공하는 코드 분할의 이점을 잃게 됩니다.

빌드 시 SSR 미들웨어 코드를 번들링할지 여부를 정의합니다.

활성화되면 미들웨어 코드가 빌드 중에 모든 페이지에서 번들링되고 가져오는 것을 방지합니다.

my-adapter.mjs
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js',
adapterFeatures: {
edgeMiddleware: true
}
});
},
},
};
}

그런 다음 후크 astro:build:ssr를 사용하면 파일 시스템의 실제 파일에 대한 middlewareEntryPoint, URL을 제공합니다.

my-adapter.mjs
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js',
adapterFeatures: {
edgeMiddleware: true
}
});
},
'astro:build:ssr': ({ middlewareEntryPoint }) => {
// 이 속성이 존재하는지 확인하세요. 어댑터가 해당 기능을 선택하지 않으면 'undefined'가 됩니다.
if (middlewareEntryPoint) {
createEdgeMiddleware(middlewareEntryPoint)
}
}
},
};
}
function createEdgeMiddleware(middlewareEntryPoint) {
// 번들러를 사용하여 새로운 실제 파일을 내보냅니다.
}