runlot
시작하기

프레임워크

Hono와 정적 사이트는 별도 어댑터 없이 배포할 수 있으며, Next.js는 @runlot/next 어댑터가 빌드를 처리합니다.

runlot의 기본 배포 단위는 워커와 정적 자산 디렉토리입니다. 프레임워크는 이 구성을 바탕으로 동작합니다.

Hono

별도 어댑터가 필요하지 않습니다. Hono 앱은 fetch 핸들러를 제공하므로 export default app으로 내보내면 됩니다.

src/index.ts
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;
runlot.json
{ "name": "my-app", "main": "src/index.ts", "assets": "public" }

정적 사이트

main 없이 assets만 지정하면 정적 사이트로 배포합니다.

runlot.json
{ "name": "my-site", "assets": "dist" }

/와 확장자 없는 경로는 index.html로 답하고, Content-Type은 확장자로 정합니다. React Router처럼 클라이언트 라우팅을 쓰면 "notFound": "spa"를 더하세요 — 자산에 없는 확장자 없는 경로가 /index.html로 답합니다 (wrangler의 not_found_handling: "single-page-application"과 같은 자리입니다).

runlot.json
{ "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 deploynext build와 OpenNext 빌드를 실행합니다. mainassets는 빌드 과정에서 생성하므로 직접 지정하지 않습니다. 프로젝트에는 next@runlot/next가 설치되어 있어야 합니다.

runlot.json
{ "name": "my-app", "framework": "next" }

Next.js 코드에서는 요청 객체를 직접 전달하지 않아도 되는 env를 사용합니다. 어댑터가 현재 요청의 컨텍스트를 처리합니다.

app/page.tsx
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 deploymain이 가리키는 파일을 esbuild로 묶으므로, virtual:react-router/server-build 같은 빌드 시점 가상 모듈은 소스 진입점에서 풀 수 없습니다.

빌드를 먼저 실행하고 main이 그 산출물을 가리키게 하세요. 서버 빌드가 fetch를 내보내는 ESM 파일 하나로 나오는 프레임워크는 저희 어댑터 없이 배포됩니다. React Router의 Cloudflare 대상이 정확히 그 모양입니다.

npm run build
runlot.json
{ "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);
React Router의 SSR 서버 빌드가 배포 번들러를 그대로 통과하는 것까지 확인했습니다. 배포 전 구간 검증은 아직입니다. 문제가 있으면 알려 주세요.

SvelteKit과 Nuxt는 이 모양을 대상으로 하는 어댑터가 필요하고, 아직 없습니다. 클라이언트 빌드만 제공하는 구성(SPA와 별도 API 워커)은 정적 사이트 방식으로 배포할 수 있습니다.

예약 실행

runlot.jsontriggers 를 적으면 워커의 scheduled 핸들러가 그 시각에 돕니다 — 예약 실행에 있습니다.

runlot.json
{ "name": "my-app", "main": "src/index.ts", "triggers": { "crons": ["30 18 * * *"] } }

이 페이지의 목차