본문으로 건너뛰기
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로 답합니다.

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 deploy가 next build와 OpenNext 빌드를 실행합니다. main과 assets는 빌드 과정에서 생성하므로 직접 지정하지 않습니다. 프로젝트에는 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 하나뿐이고, 별도의 빌드 설정 파일은 만들지 않습니다. .open-next/와 .runlot-next/는 빌드 결과물이므로 템플릿의 .gitignore에 이미 포함되어 있습니다.

next/image의 이미지 최적화는 현재 지원하지 않습니다. 이미지는 원본 파일로 제공됩니다.

프레임워크가 워커를 직접 빌드하는 경우

runlot은 프레임워크의 빌드를 대신 실행하지 않습니다. runlot deploy는 main이 가리키는 파일을 esbuild로 묶으므로, virtual:react-router/server-build 같은 빌드 시점 가상 모듈은 소스 진입점에서 풀 수 없습니다.

빌드를 먼저 실행하고 main이 그 산출물을 가리키게 하세요. 서버 빌드가 fetch를 내보내는 ESM 파일 하나로 나오는 프레임워크는 저희 어댑터 없이 배포됩니다. 프레임워크의 서버 대상을 workerd 번들로 맞추고 — React Router, Astro, SvelteKit, Nuxt 모두 하나씩 제공합니다 — 그 빌드를 실행하세요.

npm run build
runlot.json
{
  "name": "my-app",
  "main": "build/server/index.js",
  "assets": "build/client",
  "runWorkerFirst": false
}

runWorkerFirst: false가 중요한 지점입니다. runlot의 기본값은 워커 우선입니다. main이 있으면 워커가 진입점이 되고 정적 파일은 그보다 먼저 제공되지 않습니다. 프레임워크의 서버 빌드는 반대 순서를 가정하고 자신의 파일을 요청하지 않으므로, 기본값에서는 React Router 빌드가 /favicon.ico에 대해 자체 404 페이지로 응답합니다 — 그 파일은 바로 build/client에 있는데도 말입니다.

runWorkerFirst: false에서는 정확히 일치하는 파일이 우선하고 나머지는 모두 워커로 전달됩니다. 통과 처리 코드를 직접 작성할 필요가 없습니다.

이 세 키만 있으면 React Router 앱이 그대로 배포됩니다. 루트 라우트는 서버 렌더링되고 /favicon.ico는 build/client에서 반환됩니다.

같은 세 키는 workerd 서버 번들 — fetch를 내보내는 ESM 파일 하나 — 을 빌드하는 모든 프레임워크에서 동작합니다. Astro(dist/server, dist/client)는 물론 SvelteKit과 Nuxt도 포함됩니다. runlot이 하지 않는 것은 프레임워크의 빌드를 실행하는 것이므로, 빌드 타임 가상 모듈은 runlot deploy가 보기 전에 미리 해결되어 있어야 합니다.

클라이언트 빌드만 생성하는 구성(SPA와 별도의 API 워커)은 정적 사이트로 배포할 수 있습니다.

예약 실행

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

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

이 페이지의 목차