본문으로 건너뛰기
AI 도우미
🤖
안녕하세요! 프로그래밍 질문, 코드 설명, 블로그 글 검색 등을 도와드릴 수 있어요. 무엇이 궁금하신가요?
Enter를 누르거나 버튼을 클릭하여 전송 • Shift+Enter로 줄바꿈
Previous
Next
Fresh 완전 가이드 | Deno 기반 0 JavaScript 웹 프레임워크

Fresh 완전 가이드 | Deno 기반 0 JavaScript 웹 프레임워크

Fresh 완전 가이드 | Deno 기반 0 JavaScript 웹 프레임워크

이 글의 핵심

Deno 전용 차세대 웹 프레임워크 Fresh. 기본적으로 0 JavaScript를 전송하고, Islands 아키텍처로 부분 Hydration을 지원합니다. 빌드 없이 TypeScript를 바로 실행하며, Lighthouse 점수 100점을 쉽게 달성합니다.

이 글의 핵심

Fresh는 Deno 전용 0 JavaScript 웹 프레임워크입니다. Islands 아키텍처로 부분 Hydration을 지원하고, 빌드 없이 TypeScript를 바로 실행하며, Preact 컴포넌트로 초고속 웹사이트를 구축합니다. Lighthouse 100점을 쉽게 달성합니다.

목차

Fresh란?

Fresh는 2022년 Luca Casonato (Deno 팀)가 개발한 Deno 전용 웹 프레임워크입니다.

🚀 핵심 특징

1. 0 JavaScript 기본

// 정적 페이지 (JavaScript 0KB)
export default function Home() {
  return (
    <div>
      <h1>Hello Fresh!</h1>
      <p>No JavaScript sent to the client</p>
    </div>
  );
}

2. Islands 아키텍처

// 필요한 컴포넌트만 Hydration
<Counter /> {/* JavaScript 전송됨 */}
<p>Static content</p> {/* JavaScript 없음 */}

3. 빌드 없음

Next.js:
- TypeScript → Babel → Webpack → 출력
- 빌드 시간: 30초

Fresh:
- TypeScript → 바로 실행
- 빌드 시간: 0초

4. Deno 네이티브

// Deno 표준 라이브러리 직접 사용
import { serve } from 'https://deno.land/std/http/server.ts';

Fresh 시작하기

1️⃣ Deno 설치

# macOS/Linux
curl -fsSL https://deno.land/install.sh | sh

# Windows (PowerShell)
irm https://deno.land/install.ps1 | iex

# 버전 확인
deno --version

2️⃣ Fresh 프로젝트 생성

# Fresh 프로젝트 생성
deno run -A -r https://fresh.deno.dev my-fresh-app

cd my-fresh-app

# 개발 서버 실행
deno task start

프로젝트 구조

my-fresh-app/
├── routes/
│   ├── index.tsx        # 홈페이지
│   ├── about.tsx        # /about
│   └── api/hello.ts     # API 라우트
├── islands/
│   └── Counter.tsx      # 인터랙티브 컴포넌트
├── static/
│   └── logo.svg
├── deno.json
└── fresh.gen.ts         # 자동 생성

라우팅

파일 기반 라우팅

// routes/index.tsx (홈페이지)
export default function Home() {
  return (
    <div>
      <h1>Welcome to Fresh</h1>
      <p>0 JavaScript by default</p>
    </div>
  );
}

// routes/about.tsx (/about)
export default function About() {
  return <h1>About Page</h1>;
}

// routes/blog/[slug].tsx (/blog/:slug)
import { PageProps } from '$fresh/server.ts';

export default function BlogPost(props: PageProps) {
  const { slug } = props.params;
  return <h1>Post: {slug}</h1>;
}

Islands (인터랙티브 컴포넌트)

Island 생성

// islands/Counter.tsx
import { useState } from 'preact/hooks';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Island 사용

// routes/index.tsx
import Counter from '../islands/Counter.tsx';

export default function Home() {
  return (
    <div>
      <h1>Fresh Islands Demo</h1>
      
      {/* 정적 HTML (JavaScript 없음) */}
      <p>This is static content</p>
      
      {/* Island (JavaScript 전송됨) */}
      <Counter />
      
      {/* 다시 정적 HTML */}
      <p>More static content</p>
    </div>
  );
}

Handlers (서버 로직)

GET 요청

// routes/users.tsx
import { Handlers, PageProps } from '$fresh/server.ts';

interface User {
  id: number;
  name: string;
}

export const handler: Handlers<User[]> = {
  async GET(_req, ctx) {
    // API 호출 또는 DB 쿼리
    const users = await fetchUsers();
    return ctx.render(users);
  },
};

export default function UsersPage(props: PageProps<User[]>) {
  const users = props.data;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

POST 요청

// routes/users/new.tsx
import { Handlers } from '$fresh/server.ts';

export const handler: Handlers = {
  async POST(req, ctx) {
    const form = await req.formData();
    const name = form.get('name') as string;
    const email = form.get('email') as string;

    // DB에 저장
    await createUser({ name, email });

    // 리다이렉트
    return new Response('', {
      status: 303,
      headers: { Location: '/users' },
    });
  },
};

export default function NewUser() {
  return (
    <form method="POST">
      <input name="name" placeholder="Name" />
      <input name="email" type="email" placeholder="Email" />
      <button type="submit">Create</button>
    </form>
  );
}

API 라우트

// routes/api/hello.ts
import { HandlerContext } from '$fresh/server.ts';

export const handler = (req: Request, ctx: HandlerContext) => {
  return new Response(JSON.stringify({ message: 'Hello API!' }), {
    headers: { 'Content-Type': 'application/json' },
  });
};

// routes/api/users/[id].ts
export const handler = async (req: Request, ctx: HandlerContext) => {
  const { id } = ctx.params;
  
  if (req.method === 'GET') {
    const user = await db.users.findUnique({ where: { id } });
    return Response.json(user);
  }
  
  if (req.method === 'DELETE') {
    await db.users.delete({ where: { id } });
    return new Response(null, { status: 204 });
  }
  
  return new Response('Method Not Allowed', { status: 405 });
};

Deno KV (Database)

// routes/todos.tsx
import { Handlers, PageProps } from '$fresh/server.ts';

const kv = await Deno.openKv();

interface Todo {
  id: string;
  title: string;
  completed: boolean;
}

export const handler: Handlers<Todo[]> = {
  async GET(_req, ctx) {
    const todos = [];
    const iter = kv.list<Todo>({ prefix: ['todos'] });
    
    for await (const entry of iter) {
      todos.push(entry.value);
    }
    
    return ctx.render(todos);
  },
  
  async POST(req, ctx) {
    const form = await req.formData();
    const title = form.get('title') as string;
    
    const id = crypto.randomUUID();
    await kv.set(['todos', id], {
      id,
      title,
      completed: false,
    });
    
    return new Response('', {
      status: 303,
      headers: { Location: '/todos' },
    });
  },
};

export default function Todos(props: PageProps<Todo[]>) {
  const todos = props.data;
  
  return (
    <div>
      <h1>Todos</h1>
      
      <form method="POST">
        <input name="title" placeholder="New todo..." />
        <button type="submit">Add</button>
      </form>
      
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
}

Fresh vs Astro vs Next.js

기능FreshAstroNext.js
런타임Deno만Node.jsNode.js
초기 JS0KB0KB85KB
빌드❌ 없음✅ 필요✅ 필요
Islands✅ Preact✅ 다중 프레임워크
TypeScript✅ 네이티브⚠️ 컴파일⚠️ 컴파일
배포Deno DeployVercel·CloudflareVercel
생태계🌱 새로움🌿 성장 중🌳 성숙

핵심 정리

Fresh의 장점

  1. 0 JavaScript: 기본적으로 정적 HTML만 전송
  2. 빌드 없음: TypeScript를 바로 실행
  3. Islands 아키텍처: 부분 Hydration
  4. Deno 네이티브: 안전하고 빠른 런타임
  5. Lighthouse 100점: 쉽게 달성 가능

🚀 다음 단계


시작하기: deno run -A -r https://fresh.deno.dev로 5분 만에 프로젝트를 시작하고, 0 JavaScript 웹사이트를 만드세요! 🚀