← 목록으로
AI-assisted content
Nitro 딥다이브 - UnJS의 서버 엔진

들어가며

프론트엔드 개발자라면 서버를 구축할 때 Express, Fastify, Hono 같은 프레임워크를 떠올릴 것입니다. 하지만 배포 환경에 구애받지 않는 서버를 만들고 싶다면 어떨까요? Node.js, Cloudflare Workers, Vercel, Deno 어디에서든 동일한 코드로 동작하는 서버 엔진, 그것이 바로 Nitro입니다.

Nitro는 UnJS 생태계의 핵심 프로젝트로, Nuxt 3의 서버 엔진으로 잘 알려져 있습니다. 하지만 Nuxt 없이도 독립적으로 사용할 수 있는 강력한 서버 프레임워크입니다. 실제로 이 블로그도 Nitro만으로 구축되어 있습니다.

Nitro란 무엇인가

Nitro는 유니버설 자바스크립트 서버 엔진입니다. 핵심 철학은 Write once, deploy anywhere로, 하나의 코드베이스로 다양한 배포 환경에 맞게 빌드할 수 있습니다.

// nitro.config.ts
import { defineNitroConfig } from "nitropack/config";

export default defineNitroConfig({
  compatibilityDate: "2025-01-01",
  preset: "static", // node, cloudflare, vercel, deno 등으로 변경 가능
});

preset만 바꾸면 동일한 코드가 완전히 다른 런타임에서 동작합니다. 이것이 Nitro의 가장 큰 장점입니다.

핵심 기능

1. 파일 기반 라우팅

routes/ 디렉토리에 파일을 생성하면 자동으로 API 엔드포인트가 만들어집니다.

// routes/index.ts → GET /
export default defineEventHandler(() => {
  return "Hello, Nitro!";
});

// routes/posts/[slug].ts → GET /posts/:slug
export default defineEventHandler((event) => {
  const slug = event.context.params?.slug;
  return { slug };
});

Express와 달리 라우터를 수동으로 등록할 필요가 없습니다. 파일 구조가 곧 라우팅 구조입니다. 동적 파라미터는 [param] 형태로 표현합니다.

2. Auto Imports

Nitro는 자주 사용하는 유틸리티를 자동으로 가져옵니다. defineEventHandler, createError, useStorage 같은 함수를 import 없이 바로 사용할 수 있습니다.

// import 문 없이 바로 사용 가능
export default defineEventHandler(async (event) => {
  const slug = event.context.params?.slug;
  if (!slug) throw createError({ statusCode: 404 });

  const storage = useStorage("assets:content");
  const data = await storage.getItem(slug);

  return data;
});

이는 TypeScript 지원도 완벽하게 이루어져 있어 타입 추론과 자동완성이 정상적으로 동작합니다.

3. Storage Layer

Nitro의 스토리지 레이어는 unstorage를 기반으로 하며, 통합된 key-value 인터페이스를 제공합니다.

// nitro.config.ts에서 에셋 디렉토리 설정
export default defineNitroConfig({
  serverAssets: [
    {
      baseName: "content",
      dir: "./content",
    },
  ],
});
// routes/posts/[slug].ts
export default defineEventHandler(async (event) => {
  const storage = useStorage("assets:content");
  const keys = await storage.getKeys("posts");

  // 파일 시스템, Redis, S3 등 어디에 저장되든 동일한 API
  for (const key of keys) {
    const raw = await storage.getItem(key);
    // ...
  }
});

스토리지의 강력한 점은 드라이버를 교체하더라도 코드가 변하지 않는다는 것입니다. 로컬 파일 시스템에서 개발하다가, 프로덕션에서는 Redis나 Cloudflare KV로 전환할 수 있습니다.

4. Static Prerendering

Nitro는 정적 사이트 생성(SSG)도 지원합니다. preset: "static"으로 설정하면 빌드 시 모든 라우트를 HTML로 미리 렌더링합니다.

export default defineNitroConfig({
  preset: "static",
  prerender: {
    crawlLinks: true,  // 링크를 자동으로 크롤링하여 프리렌더링
    routes: ["/"],      // 시작 라우트
    failOnError: true,  // 에러 시 빌드 실패
  },
});

crawlLinks: true를 사용하면 /에서 시작하여 페이지 내의 모든 링크를 따라가며 자동으로 프리렌더링합니다. 별도의 라우트 목록을 관리할 필요가 없습니다.

5. 배포 프리셋

Nitro가 지원하는 배포 대상은 매우 다양합니다.

프리셋설명
nodeNode.js 서버
static정적 사이트 생성
vercelVercel Serverless Functions
cloudflare-pagesCloudflare Pages
cloudflare-moduleCloudflare Workers
deno-serverDeno 런타임
bunBun 런타임
netlifyNetlify Functions
aws-lambdaAWS Lambda

각 프리셋은 해당 환경에 최적화된 빌드 결과물을 생성합니다. 개발 중에는 Node.js로 작업하다가, 배포 시에만 타겟 환경을 바꿀 수 있어 벤더 락인을 피할 수 있습니다.

UnJS 생태계와의 관계

Nitro는 독립적인 프레임워크가 아닌, UnJS 생태계의 여러 라이브러리 위에 구축되어 있습니다.

  • h3: Nitro의 HTTP 프레임워크. defineEventHandler, createError 등의 유틸리티를 제공
  • unstorage: 유니버설 스토리지 레이어
  • unenv: 크로스 런타임 환경 폴리필
  • ofetch: 유니버설 fetch 라이브러리
  • hookable: 플러그인 시스템을 위한 hooks 라이브러리

각 라이브러리가 독립적으로 동작하면서도, Nitro에서 하나로 통합됩니다. 이 모듈화된 설계 덕분에 Nitro는 가볍고 유연합니다.

Nuxt와의 관계

Nuxt 3는 서버 사이드에서 Nitro를 엔진으로 사용합니다. Nuxt의 server/ 디렉토리 구조가 바로 Nitro의 routes/ 디렉토리와 동일한 방식으로 동작합니다.

하지만 Nitro를 단독으로 사용하면 Vue 프레임워크 없이 순수한 서버 애플리케이션을 만들 수 있습니다. 블로그, API 서버, 마이크로서비스 등 Vue가 필요 없는 경우에 Nitro만으로 충분합니다.

이 블로그의 Nitro 활용

이 블로그는 Nitro를 사용한 정적 사이트의 실제 사례입니다. 주요 구조를 살펴보겠습니다.

프로젝트 구조

blog/
├── content/
│   └── posts/          # MDX 형식의 블로그 글
├── routes/
│   ├── index.ts        # 홈페이지 (글 목록)
│   ├── posts/
│   │   └── [slug].ts   # 개별 글 페이지
│   ├── tags.ts         # 태그 목록 페이지
│   └── t/
│       └── [tag].ts    # 태그별 글 목록
├── utils/
│   ├── posts.ts        # 글 데이터 처리
│   ├── markdown.ts     # 자체 마크다운 파서
│   └── template.ts     # HTML 템플릿
└── nitro.config.ts     # Nitro 설정

동작 방식

  1. 빌드 시 Nitro가 content/posts/ 디렉토리의 MDX 파일을 serverAssets로 읽어들입니다
  2. 각 라우트 핸들러가 useStorage()를 통해 콘텐츠에 접근합니다
  3. 자체 구현한 마크다운 파서가 MDX를 HTML로 변환합니다
  4. preset: "static"crawlLinks: true를 통해 모든 페이지가 정적 HTML로 프리렌더링됩니다

별도의 CMS, 데이터베이스, 프론트엔드 프레임워크 없이 Nitro만으로 완전한 블로그가 구현되어 있습니다.

Express와의 비교

기존 Node.js 서버 프레임워크인 Express와 비교하면 Nitro의 특징이 더 명확해집니다.

항목ExpressNitro
라우팅수동 등록파일 기반 자동
배포 대상Node.js15+ 환경
정적 생성불가내장 지원
Import명시적Auto Import
TypeScript별도 설정기본 지원
HMR별도 설정내장

Express가 여전히 훌륭한 프레임워크이지만, 멀티 런타임 배포와 개발 경험 측면에서 Nitro가 현대적인 대안을 제공합니다.

시작하기

Nitro 프로젝트를 시작하는 것은 매우 간단합니다.

# 프로젝트 초기화
npx giget@latest nitro my-app
cd my-app
npm install

# 개발 서버 시작
npm run dev

package.json의 스크립트도 단순합니다.

{
  "scripts": {
    "dev": "nitro dev",
    "build": "nitro build",
    "preview": "nitro preview"
  }
}

마치며

Nitro는 유니버설 배포, 파일 기반 라우팅, 스토리지 추상화, Auto Import 등 현대적인 서버 개발에 필요한 기능을 갖추고 있습니다. 특히 하나의 코드베이스로 다양한 환경에 배포할 수 있다는 점은 클라우드 네이티브 시대에 매우 매력적인 특성입니다.

Nuxt의 서버 엔진으로만 알려져 있지만, 독립적으로도 충분히 강력한 프레임워크입니다. 가벼운 API 서버부터 이 블로그와 같은 정적 사이트까지, Nitro는 다양한 사용 사례를 커버합니다. Node.js 서버 프레임워크의 새로운 선택지로 고려해볼 만합니다.