EmDash는 통합용 astro.config.mjs와 콘텐츠 컬렉션용 src/live.config.ts라는 두 파일을 통해 구성됩니다.
Astro 통합
EmDash를 Astro 통합으로 구성합니다:
import { defineConfig } from "astro/config";
import emdash, { local, r2, s3 } from "emdash/astro";
import { sqlite, libsql, d1 } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
plugins: [],
}),
],
});
통합 옵션
database
필수. 데이터베이스 어댑터 구성입니다.
// SQLite (Node.js)
database: sqlite({ url: "file:./data.db" });
// PostgreSQL
database: postgres({ connectionString: process.env.DATABASE_URL });
// libSQL
database: libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
// Cloudflare D1 (@emdash-cms/cloudflare에서 가져오기)
database: d1({ binding: "DB" });
자세한 내용은 데이터베이스 옵션을 참조하세요.
storage
필수. 미디어 스토리지 어댑터 구성입니다.
// 로컬 파일 시스템(개발용)
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
// R2 바인딩(Cloudflare Workers)
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev", // optional
});
// S3-compatible (any platform) — all fields from S3_* environment variables
storage: s3()
// Or with explicit values
storage: s3({
endpoint: "https://s3.amazonaws.com",
bucket: "my-bucket",
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: "us-east-1", // optional, default: "auto"
publicUrl: "https://cdn.example.com", // optional
});
자세한 내용은 스토리지 옵션을 참조하세요.
plugins
선택 사항. EmDash 플러그인 배열.
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
auth
선택 사항. 인증 구성.
auth: {
// 자체 가입 구성
selfSignup: {
domains: ["example.com"],
defaultRole: 20, // Contributor
},
// OAuth 공급자
oauth: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
},
// 세션 구성
session: {
maxAge: 30 * 24 * 60 * 60, // 30일
sliding: true, // 활동 시 만료 재설정
},
// 또는 Cloudflare Access 사용(독점 모드)
cloudflareAccess: {
teamDomain: "myteam.cloudflareaccess.com",
audience: "your-app-audience-tag",
autoProvision: true,
defaultRole: 30,
syncRoles: false,
roleMapping: {
"Admins": 50,
"Editors": 40,
},
},
}
auth.selfSignup
이메일 도메인이 허용된 경우 사용자가 직접 등록할 수 있도록 허용합니다.
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
domains | string[] | [] | 허용된 이메일 도메인 |
defaultRole | number | 20 | 자체 가입 역할 |
selfSignup: {
domains: ["example.com", "acme.org"],
defaultRole: 20, // Contributor
}
auth.oauth
OAuth 로그인 공급자를 구성합니다.
oauth: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
}
auth.session
세션 구성.
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
maxAge | number | 2592000 (30d) | 세션 수명(초) |
sliding | boolean | true | 활동 시 만료 재설정 |
auth.cloudflareAccess
패스키 대신 Cloudflare Access를 인증 공급자로 사용합니다.
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
teamDomain | string | 필수 | 귀하의 Access 팀 도메인 |
audience | string | 필수 | 애플리케이션 Audience (AUD) 태그 |
autoProvision | boolean | true | 첫 번째 로그인 시 사용자 생성 |
defaultRole | number | 30 | 새 사용자의 기본 역할 |
syncRoles | boolean | false | 로그인할 때마다 역할 업데이트 |
roleMapping | object | — | IdP 그룹을 역할에 매핑 |
siteUrl
선택 사항. 사이트의 브라우저 지향 공개 오리진(스키마 + 호스트 + 선택적 포트, 경로 없음).
TLS 종료 역방향 프록시 뒤에서 Astro.url은 공개 주소(https://cms.example.com) 대신 내부 주소(http://localhost:4321)를 반환합니다. 이로 인해 패스키, CSRF 오리진 매칭, OAuth 리디렉션, 로그인 리디렉션, MCP 검색, 스냅샷 내보내기, sitemap, robots.txt 및 JSON-LD 구조화 데이터가 작동하지 않습니다. siteUrl을 설정하면 이 모든 문제를 한 번에 해결할 수 있습니다.
통합은 로드 시 이 값을 검증합니다: http: 또는 https: 프로토콜을 사용하는 유효한 URL이어야 하며 origin으로 정규화됩니다(경로는 제거됨).
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
});
구성에서 siteUrl이 설정되지 않은 경우 EmDash는 환경 변수를 순서대로 확인합니다: 먼저 EMDASH_SITE_URL, 그다음 SITE_URL. 이는 런타임에 공개 URL이 설정되는 컨테이너 배포에 유용합니다.
역방향 프록시 설정
Astro는 공개 호스트가 허용되는 경우에만 **X-Forwarded-***를 반영합니다. 사용자가 사용하는 호스트 이름(및 스키마)에 대해 security.allowedDomains를 구성하세요. **astro dev**에서 일치하는 **vite.server.allowedHosts**를 추가하여 Vite가 프록시 Host 헤더를 허용하도록 합니다.
allowedDomains(및 전달된 헤더)를 먼저 수정하는 것이 좋습니다. 재구성된 URL이 여전히 브라우저 오리진에서 벗어날 때 **siteUrl**을 사용하세요(일반적으로 TLS가 앞에서 종료되고 업스트림 요청이 **http://**로 유지되는 경우).
TLS를 앞에 두고 개발 서버를 루프백(astro dev --host 127.0.0.1)에 바인딩하는 것만으로도 충분합니다: 프록시는 로컬로 연결되고 **siteUrl**이 공개 HTTPS 오리진과 일치합니다.
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
security: {
allowedDomains: [
{ hostname: "cms.example.com", protocol: "https" },
{ hostname: "cms.example.com", protocol: "http" },
],
},
vite: {
server: {
allowedHosts: ["cms.example.com"],
},
},
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
}),
],
});
데이터베이스 어댑터
emdash/db에서 가져오기:
import { sqlite, libsql, postgres, d1 } from "emdash/db";
sqlite(config)
better-sqlite3을 사용하는 SQLite 데이터베이스.
| 옵션 | 유형 | 설명 |
|---|---|---|
url | string | file: 접두어가 있는 파일 경로 |
sqlite({ url: "file:./data.db" });
libsql(config)
libSQL 데이터베이스.
| 옵션 | 유형 | 설명 |
|---|---|---|
url | string | 데이터베이스 URL |
authToken | string | 인증 토큰(로컬 파일의 경우 선택 사항) |
libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
postgres(config)
연결 풀링이 포함된 PostgreSQL 데이터베이스.
| 옵션 | 유형 | 설명 |
|---|---|---|
connectionString | string | PostgreSQL 연결 URL |
host | string | 데이터베이스 호스트 |
port | number | 데이터베이스 포트 |
database | string | 데이터베이스 이름 |
user | string | 데이터베이스 사용자 |
password | string | 데이터베이스 비밀번호 |
ssl | boolean | SSL 활성화 |
pool.min | number | 최소 풀 크기(기본값: 0) |
pool.max | number | 최대 풀 크기(기본값: 10) |
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Cloudflare D1 데이터베이스. @emdash-cms/cloudflare에서 가져옵니다.
| 옵션 | 유형 | 기본값 | 설명 |
|---|---|---|---|
binding | string | — | wrangler.jsonc의 D1 바인딩 이름 |
session | string | "disabled" | 읽기 복제 모드: "disabled", "auto" 또는 "primary-first" |
bookmarkCookie | string | "__ec_d1_bookmark" | 세션 북마크의 쿠키 이름 |
// 기본
d1({ binding: "DB" });
// 읽기 복제본 사용
d1({ binding: "DB", session: "auto" });
session이 "auto" 또는 "primary-first"인 경우 EmDash는 D1 Sessions API를 사용하여 읽기 쿼리를 근처 복제본으로 라우팅합니다. 인증된 사용자는 북마크 기반으로 쓴 내용을 읽을 수 있는 일관성을 갖습니다. 자세한 내용은 데이터베이스 옵션 — 읽기 복제본을 참조하세요.
스토리지 어댑터
emdash/astro에서 가져오기:
import emdash, { local, r2, s3 } from "emdash/astro";
local(config)
로컬 파일 시스템 저장소.
| 옵션 | 유형 | 설명 |
|---|---|---|
directory | string | 디렉토리 경로 |
baseUrl | string | 파일 제공을 위한 기본 URL |
local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
r2(config)
Cloudflare R2 바인딩.
| 옵션 | 유형 | 설명 |
|---|---|---|
binding | string | R2 바인딩 이름 |
publicUrl | string | 선택적 공개 URL |
r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
s3(config?)
S3 호환 스토리지. 모든 구성 필드는 선택 사항입니다: s3({...})에서 생략된 필드는 Node 프로세스 시작 시 해당하는 S3_* 환경 변수에서 해석됩니다. 명시적 값이 항상 우선합니다.
전제 조건: 프로젝트에 @aws-sdk/client-s3와 @aws-sdk/s3-request-presigner를 설치하세요. EmDash 코어는 AWS SDK를 번들하지 않습니다. 자세한 내용은 스토리지 옵션 → S3 호환 스토리지를 참조하세요.
| 옵션 | 유형 | 설명 |
|---|---|---|
endpoint | string | S3 엔드포인트 URL (S3_ENDPOINT) |
bucket | string | 버킷 이름 (S3_BUCKET) |
accessKeyId | string | 액세스 키 (S3_ACCESS_KEY_ID) |
secretAccessKey | string | 비밀 키 (S3_SECRET_ACCESS_KEY) |
region | string | 지역, 기본값 "auto" (S3_REGION) |
publicUrl | string | 선택적 CDN URL (S3_PUBLIC_URL) |
// All fields from S3_* environment variables (Node container deployments)
s3()
// Mix: CDN from config, rest from environment
s3({ publicUrl: "https://cdn.example.com" })
// All explicit (unchanged from before)
s3({
endpoint: "https://xxx.r2.cloudflarestorage.com",
bucket: "media",
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
publicUrl: "https://cdn.example.com",
})
런타임 환경 변수 해석은 Node 전용 기능입니다. Cloudflare Workers에서는 시크릿과 변수가 process.env가 아닌 fetch 핸들러의 env 매개변수를 통해 노출되므로 S3_* 환경 변수가 읽히지 않습니다. Workers 배포에서는 r2(config) 어댑터를 사용하거나 s3({...})에 명시적 값을 전달해야 합니다. 자세한 내용은 스토리지 옵션을 참조하세요.
라이브 컬렉션
src/live.config.ts에서 EmDash 로더를 구성합니다:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
로더 옵션
emdashLoader() 함수는 선택적 구성을 허용합니다:
emdashLoader({
// 현재 옵션 없음 - 향후 사용을 위해 예약됨
});
환경 변수
EmDash는 다음 환경 변수를 사용합니다:
| 변수 | 설명 |
|---|---|
EMDASH_SITE_URL | 브라우저 지향 공개 오리진 (SITE_URL로 폴백) |
EMDASH_DATABASE_URL | 데이터베이스 URL 재정의 |
EMDASH_AUTH_SECRET | 패스키 인증을 위한 시크릿 |
EMDASH_PREVIEW_SECRET | 미리보기 토큰 생성을 위한 시크릿 |
EMDASH_URL | 스키마 동기화를 위한 원격 EmDash URL |
다음을 사용하여 인증 시크릿을 생성합니다:
npx emdash auth secret
package.json 구성
package.json의 선택적 구성:
{
"emdash": {
"label": "My Blog Template",
"description": "A clean, minimal blog template",
"seed": ".emdash/seed.json",
"url": "https://my-site.pages.dev",
"preview": "https://emdash-blog.pages.dev"
}
}
| 옵션 | 설명 |
|---|---|
label | 표시용 템플릿 이름 |
description | 템플릿 설명 |
seed | 시드 JSON 파일의 경로 |
url | 스키마 동기화를 위한 원격 URL |
preview | 템플릿 미리보기를 위한 데모 사이트 URL |
TypeScript 구성
EmDash는 .emdash/types.ts에서 타입을 생성합니다. tsconfig.json에 다음을 추가하세요:
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
다음을 사용하여 타입을 생성합니다:
npx emdash types