설정과 의존성
웹사이트별 환경변수, 암호화 Secret, 관리자 알람과 npm package를 관리합니다.
환경변수와 Secret
- 내 사이트의 설정 버튼은 현재 목록 위에 설정 팝업을 열며, 오른쪽 펼침 버튼은 웹사이트 관리 메뉴를 표시합니다.
- 웹사이트 설정은 도메인, 환경변수, Secret, CORS 설정, 소셜 로그인, Telegram 알람, Gmail 설정과 고급 설정 탭으로 나뉘며 탭을 이동해도 저장 전 입력 상태를 유지합니다.
- 도메인 탭 하단에서 Cloudflare 도메인 관리 페이지를 바로 열어 DNS 레코드를 추가하거나 변경할 수 있습니다.
- CORS 설정 탭에는 외부 웹앱의 정확한 Origin을 프로토콜과 포트까지 포함해 한 줄씩 입력합니다.
- 에디터의 설정 버튼이나 MCP update_settings에서 일반 환경변수와 Secret을 분리해 저장합니다.
- Secret은 AES-256-GCM으로 암호화하며 저장 후 값은 다시 표시하지 않습니다.
- SERVICE_SECRET_KEY를 우선 사용하고 없으면 MANAGER_PASSWORD에서 암호화 키를 만듭니다.
- DATABASE_URL, REDIS_URL, CAKE_ 접두어 등 내부 변수는 웹사이트가 덮어쓸 수 없습니다.
- Redis 계정과 웹사이트별 키 접근 권한은 Cake20이 자동 생성하고 갱신합니다.
- Google, GitHub와 네이버 로그인 키는 소셜 로그인 전용 입력란에서 암호화해 저장하며 공급자별 콜백 URL을 함께 확인할 수 있습니다.
- Telegram 알람 탭은 BotFather API Key와 사이트 관리자 개인 채팅을 연결하고 테스트 알람을 보낼 수 있습니다.
- Gmail 설정 탭은 사이트 소유자의 전송 전용 Google OAuth 권한을 연결하고 보유 사이트별 활성화와 테스트 메일을 제공합니다.
- 파일·DB·Redis·메모리·CPU·요청 제한은 관리자가 무료·구독 웹사이트별로 설정하며, 상단 구독플랜에서 현재 값을 비교할 수 있습니다.
- 잠자기는 웹사이트별 사용자 설정이 아닌 Cake20 엔진의 내부 운영 정책입니다.
- 환경변수와 실행 설정은 다음 시작 때 적용됩니다.
MCP get_settings는 일반 환경변수와 Secret 이름만 반환합니다. update_settings의 secrets에는 새 값 또는 삭제할 이름의 null만 보내며, 기존 Secret 값은 읽거나 다시 전달할 필요가 없습니다.
npm 의존성
로컬 프로젝트의 package name은 첫 원격 deploy 전까지 사용할 수 있습니다. Cake20 Core가 웹사이트를 발급하면 package.json name을 web-{웹사이트ID}로 바꾸고 시스템 식별자로 관리합니다. 별도 id 필드를 만들지 않습니다.
루트 package.json에 registry package를 선언하면 release 안에 production dependency를 설치합니다. dependency lifecycle script는 실행하지 않습니다. Cake20 View, Cake20 UI, Prisma와 Zod는 런타임 제공 버전을 사용합니다. 외부 패키지는 관리자가 features.externalPackages를 활성화한 경우에만 사용할 수 있으며, 기본값은 비활성화입니다.
{
"dependencies": {
"nanoid": "^5.1.5"
}
}Cake20 CDN
- 브라우저 기능을 추가하기 전에 Cake20 CDN 카탈로그를 먼저 확인합니다. 이미 제공되는 모듈과 폰트는 npm 의존성을 중복 설치하지 않고 버전이 명시된 CDN 경로를 우선 사용합니다.
- Cake20 CDN은 여러 웹사이트가 함께 쓰는 브라우저 전용 정적 모듈과 폰트를 버전 경로로 제공합니다.
- CodeMirror 번들은 /editor/v1/editor.mjs, Tiptap Core·StarterKit과 주요 확장 번들은 /editor/v1/rich-editor.mjs에서 불러옵니다.
- DataTables와 Responsive 번들은 /datatable/v1/datatable.mjs, 스타일은 /datatable/v1/datatable.css?v=1에서 불러옵니다.
- 공통 폰트는 /fonts/v1/ibm-plex-sans.css와 /fonts/v1/lilex.css를 app/assets/style.css 맨 위에서 @import url(...)로 불러올 수 있습니다.
- 원격 ESM은 Cake20 View의 브라우저 실행 구간에서만 동적 import하고 Vite가 URL을 그대로 유지하도록 @vite-ignore를 사용합니다.
- @tiptap/extension-text-align은 일반 import 문법을 유지하면 Runtime이 /editor/v1/tiptap-text-align.mjs로 연결합니다. 이 모듈도 app 코드에서만 사용하고 package.json dependencies에는 추가하지 않습니다.
- 서버 API, task와 job에서는 CDN 모듈을 import하지 않습니다. 서버에서 필요한 기능은 Runtime 인터페이스나 허용된 npm 의존성을 사용합니다.
- CDN에 제공되는 브라우저 모듈을 package.json dependencies에 중복으로 추가하지 않습니다. v1 같은 버전 경로는 고정된 계약으로 취급합니다.
- 일반 편집 화면은 Cake20 UI의 기존 Editor를 우선 사용하고, rich-editor는 낮은 수준의 커스텀 편집기가 필요할 때 사용합니다.
- 드래그 정렬은 CDN 패키지가 아니라 Cake20 내장 <DragList>를 import 없이 사용하며 별도 드래그 라이브러리를 추가하지 않습니다.
onMounted(async () => {
const rich = await import(
/* @vite-ignore */
"https://cdn.cake20.com/editor/v1/rich-editor.mjs"
);
const { Editor, StarterKit, TextAlign } = rich;
});CDN 파일은 브라우저에 공개됩니다. API Key, Secret, 인증정보나 사용자별 비공개 데이터를 CDN 코드와 URL에 포함하지 않습니다.
DataTables CDN과 서버 사이드 목록
- DataTables 3와 Responsive 4 번들은 Cake20 CDN의 버전 경로에서 불러오며 package.json dependencies에 중복으로 추가하지 않습니다.
- 브라우저 전용 app/components/DataTable.client.tsx에서 CSS를 한 번 연결하고 ES Module을 @vite-ignore 동적 import로 불러옵니다.
- 공용 서비스의 목록은 데이터가 적어도 serverSide: true를 기본으로 하여 브라우저가 전체 행을 먼저 받지 않도록 구성합니다.
- 요청은 { page, take, find, date, order }, 응답은 { total, items } 계약을 사용하고 DataTables의 start와 draw는 래퍼 내부에서만 변환합니다.
- 검색과 정렬 정보도 서버에 보내며 서버는 허용한 필드만 검증해 정렬합니다.
- 전체 구현은 템플릿 목록의 DataTable 상품소개에서 한국어와 영어 예제로 확인할 수 있습니다.
const style = document.createElement("link");
style.rel = "stylesheet";
style.href = "https://cdn.cake20.com/datatable/v1/datatable.css?v=1";
document.head.append(style);
const DataTable = (await import(
/* @vite-ignore */
"https://cdn.cake20.com/datatable/v1/datatable.mjs"
)).default;
new DataTable(table, {
serverSide: true,
pageLength: 5,
responsive: true,
ajax: async (input, done) => {
const reply = await fetchPost("/api/products", {
page: Math.floor(input.start / input.length) + 1,
take: input.length,
find: input.search.value
? [{ field: "all", value: input.search.value }]
: [],
order: input.order.map((item) => ({
field: columns[item.column].data,
dir: item.dir
}))
});
done({
draw: input.draw,
recordsTotal: reply.total,
recordsFiltered: reply.total,
data: reply.items
});
},
columns
});DataTables와 Responsive의 MIT 라이선스는 https://cdn.cake20.com/datatable/v1/LICENSE.txt에서 확인합니다.