프레임워크
Hono와 정적 사이트는 별도 어댑터 없이 배포할 수 있으며, Next.js는 @runlot/next 어댑터가 빌드를 처리합니다.
runlot의 기본 배포 단위는 워커와 정적 자산 디렉토리입니다. 프레임워크는 이 구성을 바탕으로 동작합니다.
Hono
별도 어댑터가 필요하지 않습니다. Hono 앱은 fetch 핸들러를 제공하므로 export default app으로 내보내면 됩니다.
import { Hono } from "hono";
import { Pool } from "@runlot/pg";
const app = new Hono<{ Bindings: Env }>();
app.get("/posts", async (c) => {
const user = await c.env.auth.user(c.req.raw);
const db = new Pool({ db: c.env.db });
const { rows } = await db.query(
"select id, title from posts where owner = $1 limit 20",
[user?.id ?? null],
);
return c.json(rows);
});
export default app;{ "name": "my-app", "main": "src/index.ts", "assets": "public" }정적 사이트
main 없이 assets만 지정하면 정적 사이트로 배포합니다.
{ "name": "my-site", "assets": "dist" }/와 확장자 없는 경로는 index.html로 답하고, Content-Type은 확장자로 정합니다. React Router처럼 클라이언트 라우팅을 쓰면 "notFound": "spa"를 더하세요 — 자산에 없는 확장자 없는 경로가 /index.html로 답합니다 (wrangler의 not_found_handling: "single-page-application"과 같은 자리입니다).
{ "name": "my-site", "assets": "dist", "notFound": "spa" }Vite, Astro, 11ty처럼 빌드 과정이 있는 정적 사이트는 빌드한 뒤 출력 디렉토리를 assets로 지정하세요.
npm run build
runlot deploy정적 사이트는 정적 자산만 포함한 runlot 배포입니다. 나중에 워커 코드를 추가해도 서비스를 옮기지 않고 같은 프로젝트에서 계속 배포할 수 있습니다.
Next.js
npm create runlot@latest에서 next 템플릿을 선택하면 아래 구성이 생성됩니다. 실제 런타임 환경 검증은 진행 중입니다. 첫 배포 중 문제가 발생하면 알려 주세요.runlot.json에 "framework": "next"를 지정하면 runlot deploy가 next build와 OpenNext 빌드를 실행합니다. main과 assets는 빌드 과정에서 생성하므로 직접 지정하지 않습니다. 프로젝트에는 next와 @runlot/next가 설치되어 있어야 합니다.
{ "name": "my-app", "framework": "next" }Next.js 코드에서는 요청 객체를 직접 전달하지 않아도 되는 env를 사용합니다. 어댑터가 현재 요청의 컨텍스트를 처리합니다.
import { env } from "@runlot/next";
import { Pool } from "@runlot/pg";
export default async function Page() {
const user = await env.auth.user();
const db = new Pool({ db: env.db });
const { rows } = await db.query("select id, title from posts limit 20");
return <main>{user?.email} · {rows.length}</main>;
}env는 요청을 처리하는 동안에만 사용할 수 있습니다. 모듈 최상위 영역이나 빌드 시점의 정적 렌더링에서 읽으면 오류가 발생합니다.
ISR은 env.storage를, 태그 재검증은 env.db를 사용합니다. runlot.json에 "database": true와 "storage": true를 적으세요. 추가하지 않으면 정적 자산 캐시를 사용하며 배포 로그에 안내가 표시됩니다. 별도의 설정 파일(wrangler.toml)은 만들지 않습니다. .open-next/와 .runlot-next/는 빌드 결과물이므로 템플릿의 .gitignore에 이미 포함되어 있습니다.
next/image의 이미지 최적화는 현재 지원하지 않습니다. 이미지는 원본 파일로 제공됩니다.
프레임워크가 워커를 직접 빌드하는 경우
runlot은 프레임워크의 빌드를 대신 실행하지 않습니다. runlot deploy는 main이 가리키는 파일을 esbuild로 묶으므로, virtual:react-router/server-build 같은 빌드 시점 가상 모듈은 소스 진입점에서 풀 수 없습니다.
빌드를 먼저 실행하고 main이 그 산출물을 가리키게 하세요. 서버 빌드가 fetch를 내보내는 ESM 파일 하나로 나오는 프레임워크는 저희 어댑터 없이 배포됩니다. React Router의 Cloudflare 대상이 정확히 그 모양입니다.
npm run build{ "name": "my-app", "main": "build/server/index.js", "assets": "build/client" }wrangler와 다른 자리가 하나 있습니다. main이 있으면 워커가 진입점이고 정적 자산은 그 앞에서 서빙되지 않습니다. 요청을 프레임워크에 넘기기 전에 워커에서 직접 내보내세요. 자세한 내용은 정적 자산에 있습니다.
const url = new URL(request.url);
if (url.pathname.startsWith("/assets/")) return env.assets.fetch(request);
return handler(request);SvelteKit과 Nuxt는 이 모양을 대상으로 하는 어댑터가 필요하고, 아직 없습니다. 클라이언트 빌드만 제공하는 구성(SPA와 별도 API 워커)은 정적 사이트 방식으로 배포할 수 있습니다.
예약 실행
runlot.json 에 triggers 를 적으면 워커의 scheduled 핸들러가 그 시각에 돕니다 — 예약 실행에 있습니다.
{ "name": "my-app", "main": "src/index.ts", "triggers": { "crons": ["30 18 * * *"] } }