Back

Edge Runtime vs Node.js Runtime: ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜๊ฐ€ ๊ฐ‘์ž๊ธฐ ์•ˆ ๋Œ์•„๊ฐ€๋Š” ์ด์œ 

Edge Runtime vs Node.js Runtime: ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜๊ฐ€ ๊ฐ‘์ž๊ธฐ ์•ˆ ๋Œ์•„๊ฐ€๋Š” ์ด์œ 

๋‹ค๋“ค ํ•œ ๋ฒˆ์ฏค ๋“ค์–ด๋ดค์„ ๊ฒ๋‹ˆ๋‹ค. Edge ํ•จ์ˆ˜๋Š” ๋น ๋ฅด๊ณ , ์ €๋ ดํ•˜๊ณ , ์œ ์ € ๊ฐ€๊นŒ์ด์—์„œ ๋Œ์•„๊ฐ„๋‹ค๊ณ . ์ฝœ๋“œ ์Šคํƒ€ํŠธ๋„ ์ดˆ ๋‹จ์œ„๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๊ณ , ๊ธ€๋กœ๋ฒŒ ๋ฐฐํฌ๊ฐ€ ๊ธฐ๋ณธ์ด๋ผ๊ณ . ๊ทธ๋ž˜์„œ Next.js API ๋ผ์šฐํŠธ์— export const runtime = 'edge' ๋”ฑ ํ•œ ์ค„ ๋„ฃ๊ณ  ๋ฐฐํฌํ–ˆ๋Š”๋ฐ...

๋‹ค ํ„ฐ์กŒ์Šต๋‹ˆ๋‹ค.

Edge Runtime ๋””๋ฒ„๊น… ์ง€์˜ฅ์— ์˜ค์‹  ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋„ ์•”ํ˜ธ ๊ฐ™์ฃ . "Dynamic code evaluation not supported." "Module not found." "This API is not available." ๋ถ„๋ช… ๋กœ์ปฌ์—์„œ ์ž˜ ๋Œ์•„๊ฐ”๋Š”๋ฐ. Node.js์—์„œ๋„ ๋ฌธ์ œ์—†์—ˆ๋Š”๋ฐ. Edge๋งŒ ๋ถ™์ด๋ฉด ์™œ ์ด๋Ÿฌ๋Š” ๊ฑธ๊นŒ์š”?

Edge Runtime ์š•ํ•˜๋ ค๋Š” ๊ฑด ์•„๋‹™๋‹ˆ๋‹คโ€”์ง„์งœ ์ข‹์€ ๊ธฐ์ˆ ์ด์—์š”. ๊ทผ๋ฐ "Edge ๋น ๋ฆ„" ๋งˆ์ผ€ํŒ…์ด๋ž‘ ์‹ค์ œ๋กœ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ ๋Œ๋ฆฌ๋Š” ํ˜„์‹ค ์‚ฌ์ด์— ์—„์ฒญ๋‚œ ๊ฐญ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜ ๊ทธ ๊ฐญ์„ ๋ฉ”์›Œ๋ด…์‹œ๋‹ค.

์ผ๋‹จ ๊ฐœ๋…๋ถ€ํ„ฐ: Edge Runtime์ด ๋ญ”๋ฐ?

๋””๋ฒ„๊น… ์ „์— ๋ญ˜ ๋‹ค๋ฃจ๋Š”์ง€๋ถ€ํ„ฐ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. Edge Runtime์ด๋ž‘ Node.js Runtime์€ ๋‹จ์ˆœํžˆ ์ฝ”๋“œ ๋Œ์•„๊ฐ€๋Š” ์œ„์น˜๋งŒ ๋‹ค๋ฅธ ๊ฒŒ ์•„๋‹™๋‹ˆ๋‹ค. ์™„์ „ํžˆ ๋‹ค๋ฅธ ์‹คํ–‰ ํ™˜๊ฒฝ์ด์—์š”. API๋„ ๋‹ค๋ฅด๊ณ , ์ œ์•ฝ๋„ ๋‹ค๋ฅด๊ณ , ๋จธ๋ฆฟ์† ๊ทธ๋ฆผ ์ž์ฒด๊ฐ€ ๋‹ฌ๋ผ์•ผ ํ•ด์š”.

Node.js Runtime: ์žˆ์„ ๊ฑด ๋‹ค ์žˆ์Œ

Node.js ๋Ÿฐํƒ€์ž„์€ ์šฐ๋ฆฌ๊ฐ€ ์•„๋Š” ๊ทธ๊ฑฐ. ์„œ๋ฒ„์—์„œ ๋Œ์•„๊ฐ€๋Š” ํ’€์˜ต์…˜ Node.js ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค:

// Node.js ๋Ÿฐํƒ€์ž„์—์„  ์ด๊ฑฐ ๋‹ค ๋จ import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; import { spawn } from 'child_process'; export async function POST(request) { // ํŒŒ์ผ ์ฝ๊ธฐ? ๋จ const config = JSON.parse(fs.readFileSync('./config.json', 'utf8')); // ๋„ค์ดํ‹ฐ๋ธŒ crypto? ๋จ const hash = crypto.createHash('sha256').update('secret').digest('hex'); // ํ”„๋กœ์„ธ์Šค ๋„์šฐ๊ธฐ? ๋จ const child = spawn('ls', ['-la']); // npm ํŒจํ‚ค์ง€ ์•„๋ฌด๊ฑฐ๋‚˜? ๋จ const pdf = await generatePDF(data); return Response.json({ success: true }); }

Node.js ๋Ÿฐํƒ€์ž„์œผ๋กœ ์–ป๋Š” ๊ฒƒ:

  • Node.js ์ฝ”์–ด ๋ชจ๋“ˆ ์ „๋ถ€ (fs, path, crypto, child_process ๋“ฑ)
  • ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ (C++ ์• ๋“œ์˜จ, NAPI๋กœ Rust ๋ฐ”์ธ๋”ฉ)
  • ์‚ฌ์‹ค์ƒ ์ฝ”๋“œ ํฌ๊ธฐ ๋ฌด์ œํ•œ
  • ๊ธด ํƒ€์ž„์•„์›ƒ (์›ฌ๋งŒํ•˜๋ฉด 5๋ถ„๊นŒ์ง€)
  • ๋„ค์ดํ‹ฐ๋ธŒ ๋“œ๋ผ์ด๋ฒ„๋กœ DB ์—ฐ๊ฒฐ
  • ์Šคํƒ ํŠธ๋ ˆ์ด์Šค ์ œ๋Œ€๋กœ ๋‚˜์˜ค๋Š” ๋””๋ฒ„๊น…

๋Œ€์‹ ? ์ฝœ๋“œ ์Šคํƒ€ํŠธ 250ms~1์ดˆ+, ๋ฆฌ์ „๋ณ„ ๋ฐฐํฌ๋ผ ๊ธ€๋กœ๋ฒŒ ์•„๋‹˜, ์Šค์ผ€์ผ ์ปค์ง€๋ฉด ๋น„์šฉ๋„ ์ปค์ง.

Edge Runtime: ๊ฐ€๋ณ๊ณ  ๋น ๋ฆ„, ๋Œ€์‹ ...

Edge Runtime์€ ์™„์ „ ๋‹ค๋ฅธ ๋…€์„์ž…๋‹ˆ๋‹ค. Web API๋ž‘ V8 isolates ๊ธฐ๋ฐ˜โ€”Cloudflare Workers ๋Œ๋ฆฌ๋Š” ๊ธฐ์ˆ ์ด๋ž‘ ๊ฐ™์•„์š”:

// Edge Runtime์—์„  ์ด๊ฒŒ ์ „๋ถ€ export const runtime = 'edge'; export async function POST(request) { // Web Fetch API? ๋จ const response = await fetch('https://api.example.com/data'); // Web Crypto API? ๋จ (๊ทผ๋ฐ Node crypto๋ž‘ ๋‹ค๋ฆ„!) const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('secret') ); // Headers, Request, Response? ๋จ const headers = new Headers(); headers.set('Cache-Control', 'max-age=3600'); return new Response(JSON.stringify({ success: true }), { headers }); }

Edge Runtime์œผ๋กœ ์–ป๋Š” ๊ฒƒ:

  • Web Platform API๋งŒ (Fetch, Streams, Crypto, URL ๋“ฑ)
  • 10ms๋„ ์•ˆ ๊ฑธ๋ฆฌ๋Š” ์ฝœ๋“œ ์Šคํƒ€ํŠธ
  • ๊ธ€๋กœ๋ฒŒ ๋ฐฐํฌ (300๊ตฐ๋ฐ+์—์„œ ์‹คํ–‰)
  • ์Šค์ผ€์ผ ์ปค์ ธ๋„ ๋น„์šฉ ๋‚ฎ์Œ
  • 1~4MB ์ฝ”๋“œ ํฌ๊ธฐ ์ œํ•œ
  • ์งง์€ ํƒ€์ž„์•„์›ƒ (๋ณดํ†ต 30์ดˆ ํ•œ๊ณ„)

๋Œ€์‹ ? fs ์—†์Œ, ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ ์—†์Œ, Node.js ์ฝ”์–ด ๋ชจ๋“ˆ ์—†์Œ, npm ํŒจํ‚ค์ง€ ์ ˆ๋ฐ˜์€ ์•ˆ ๋Œ์•„๊ฐ.

์™œ ํ„ฐ์ง€๋Š”๊ฐ€: ์‹คํŒจ ์œ ํ˜•๋ณ„ ์ •๋ฆฌ

ํ„ฐ์ง€๋Š” ์ด์œ ๋ฅผ ์œ ํ˜•๋ณ„๋กœ ์ •๋ฆฌํ•ด๋ด…์‹œ๋‹ค. ๋ญ๊ฐ€ ๋ฌธ์ œ์ธ์ง€ ์•Œ์•„์•ผ ๊ณ ์น˜์ฃ .

์œ ํ˜• 1: Node.js ์ฝ”์–ด ๋ชจ๋“ˆ์ด ์—†์Œ

๊ฐ€์žฅ ํ”ํ•ฉ๋‹ˆ๋‹ค. Edge์— ์—†๋Š” ๊ฑธ import ํ•˜๋Š” ๊ฒฝ์šฐ:

// โŒ Edge์—์„œ ์ „๋ถ€ ์•ˆ ๋จ import fs from 'fs'; // ํŒŒ์ผ์‹œ์Šคํ…œ ์—†์Œ import path from 'path'; // path ๋ชจ๋“ˆ ์—†์Œ import crypto from 'crypto'; // crypto API ๋‹ค๋ฆ„ import { Buffer } from 'buffer'; // Buffer ์ผ๋ถ€๋งŒ ์ง€์› import stream from 'stream'; // Node streams ์—†์Œ import http from 'http'; // http ๋ชจ๋“ˆ ์—†์Œ import https from 'https'; // https ๋ชจ๋“ˆ ์—†์Œ import net from 'net'; // TCP ์†Œ์ผ“ ์—†์Œ import dns from 'dns'; // DNS ์กฐํšŒ ์—†์Œ import child_process from 'child_process'; // ํ”„๋กœ์„ธ์Šค ์—†์Œ import os from 'os'; // OS ์ •๋ณด ์—†์Œ import worker_threads from 'worker_threads'; // ์›Œ์ปค ์Šค๋ ˆ๋“œ ์—†์Œ

ํ•ด๊ฒฐ: Web API๋กœ ๋Œ€์ฒดํ•˜๊ฑฐ๋‚˜ ํด๋ฆฌํ•„ ์“ฐ๊ธฐ:

// โœ… Edge์—์„œ ์“ธ ์ˆ˜ ์žˆ๋Š” ๋Œ€์ฒด์ œ // crypto.createHash() ๋Œ€์‹  async function sha256(message) { const msgBuffer = new TextEncoder().encode(message); const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } // Buffer.from() ๋Œ€์‹  function base64Encode(str) { return btoa(String.fromCharCode(...new TextEncoder().encode(str))); } function base64Decode(base64) { return new TextDecoder().decode( Uint8Array.from(atob(base64), c => c.charCodeAt(0)) ); } // path.join() ๋Œ€์‹  function joinPath(...segments) { return segments.join('/').replace(/\/+/g, '/'); } // querystring ๋Œ€์‹  URL ํŒŒ์‹ฑ function parseQuery(url) { return Object.fromEntries(new URL(url).searchParams); }

์œ ํ˜• 2: ๋™์  ์ฝ”๋“œ ์‹คํ–‰ ๊ธˆ์ง€

Edge Runtime์€ ๋ณด์•ˆ์ƒ eval()์ด๋ž‘ new Function() ๋ชป ์”๋‹ˆ๋‹ค. ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์€ ํŒจํ‚ค์ง€๊ฐ€ ์ด๊ฑฐ ๋•Œ๋ฌธ์— ํ„ฐ์ ธ์š”:

// โŒ Edge์—์„œ ์ „๋ถ€ ์•ˆ ๋จ eval('console.log("hello")'); new Function('return 1 + 1')(); require('vm').runInNewContext('1 + 1'); // vm ๋ชจ๋“ˆ๋„ ์—†์Œ // ์ด๋Ÿฐ ํŒจํ‚ค์ง€๋“ค์ด ๋‚ด๋ถ€์—์„œ ์”€: // - ์ผ๋ถ€ ํ…œํ”Œ๋ฆฟ ์—”์ง„ (Handlebars, ํŠน์ • ๋ชจ๋“œ์˜ EJS) // - ์ผ๋ถ€ ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ // - ์ผ๋ถ€ ์ง๋ ฌํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ // - ์†Œ์Šค๋งต ์ฒ˜๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

์—๋Ÿฌ ๋ฉ”์‹œ์ง€:

Dynamic code evaluation (e.g., 'eval', 'new Function', 'WebAssembly.compile') 
not allowed in Edge Runtime

ํ•ด๊ฒฐ: ๋Œ€์ฒด ํŒจํ‚ค์ง€ ์ฐพ๊ธฐ:

// lodash template ์“ฐ๋ฉด ์•ˆ ๋จ (๋‚ด๋ถ€์—์„œ new Function ์”€) // โŒ import template from 'lodash/template'; const compiled = template('Hello <%= name %>'); // โœ… ์ •์  ํ…œํ”Œ๋ฆฟ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๊ต์ฒด import Mustache from 'mustache'; const output = Mustache.render('Hello {{name}}', { name: 'World' }); // ์•„๋‹ˆ๋ฉด ๊ทธ๋ƒฅ ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด ์“ฐ๊ธฐ function html(strings, ...values) { return strings.reduce((result, str, i) => result + str + (values[i] ?? ''), '' ); } const name = 'World'; const output = html`Hello ${name}`;

์œ ํ˜• 3: ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ

C++/Rust ๋ฐ”์ธ๋”ฉ ์“ฐ๋Š” ํŒจํ‚ค์ง€๋Š” Edge์—์„œ ์•ˆ ๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค:

// โŒ Edge์—์„œ ์•ˆ ๋˜๋Š” ํŒจํ‚ค์ง€๋“ค import sharp from 'sharp'; // ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ (๋„ค์ดํ‹ฐ๋ธŒ) import bcrypt from 'bcrypt'; // ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ (๋„ค์ดํ‹ฐ๋ธŒ) import canvas from 'canvas'; // ์บ”๋ฒ„์Šค ๋ Œ๋”๋ง (๋„ค์ดํ‹ฐ๋ธŒ) import sqlite3 from 'sqlite3'; // SQLite (๋„ค์ดํ‹ฐ๋ธŒ) import puppeteer from 'puppeteer'; // ๋ธŒ๋ผ์šฐ์ € ์ž๋™ํ™” (๋„ค์ดํ‹ฐ๋ธŒ) import prisma from '@prisma/client'; // Prisma (์ฟผ๋ฆฌ ์—”์ง„์ด ๋„ค์ดํ‹ฐ๋ธŒ)

์—๋Ÿฌ ๋ฉ”์‹œ์ง€:

Error: Cannot find module 'sharp'
Module build failed: Native modules are not supported in Edge Runtime

ํ•ด๊ฒฐ: Edge์—์„œ ๋Œ์•„๊ฐ€๋Š” ๋Œ€์ฒด์ œ ์“ฐ๊ธฐ:

// โœ… Edge ํ˜ธํ™˜ ๋Œ€์ฒด์ œ // bcrypt ๋Œ€์‹  (์ˆœ์ˆ˜ JS ๊ตฌํ˜„) import { hash, compare } from 'bcryptjs'; // sharp ๋Œ€์‹  // ํด๋ผ์šฐ๋“œ ์ด๋ฏธ์ง€ ์„œ๋น„์Šค๋‚˜ WASM ๊ธฐ๋ฐ˜ ์†”๋ฃจ์…˜ ์“ฐ๊ธฐ async function resizeImage(imageUrl, width, height) { const response = await fetch( `https://images.example.com/resize?url=${encodeURIComponent(imageUrl)}&w=${width}&h=${height}` ); return response; } // Prisma ๋Œ€์‹  // Edge ํ˜ธํ™˜ ๋“œ๋ผ์ด๋ฒ„ ์–ด๋Œ‘ํ„ฐ ์“ฐ๊ธฐ import { PrismaClient } from '@prisma/client'; import { PrismaNeon } from '@prisma/adapter-neon'; // ๋˜๋Š” ์„œ๋ฒ„๋ฆฌ์Šค ์นœํ™”์  ORM import { drizzle } from 'drizzle-orm/neon-http'; // SQLite ๋Œ€์‹  // D1 (Cloudflare), Turso, PlanetScale ์“ฐ๊ธฐ

์œ ํ˜• 4: ๋ธ”๋กœํ‚น ์—ฐ์‚ฐ

Edge Runtime์€ ๋น ๋ฅธ ๋…ผ๋ธ”๋กœํ‚น ์ž‘์—…์šฉ์ž…๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ ๋ฃจํ”„ ๋ง‰๋Š” ๊ฑด ๋ฌธ์ œ๋จ:

// โŒ Edge์—์„œ ๋ฌธ์ œ๋˜๋Š” ํŒจํ„ด import { readFileSync } from 'fs'; // ๋™๊ธฐ ํŒŒ์ผ ์ฝ๊ธฐ (+ fs๋„ ์—†์Œ) sleep(1000); // ๋ธ”๋กœํ‚น sleep // ๋ฌด๊ฑฐ์šด ๋™๊ธฐ ์—ฐ์‚ฐ function fibonacciSync(n) { if (n <= 1) return n; return fibonacciSync(n - 1) + fibonacciSync(n - 2); } const result = fibonacciSync(45); // ๋ช‡ ์ดˆ๊ฐ„ ๋ธ”๋กœํ‚น // ๊ฑฐ๋Œ€ํ•œ JSON ๋™๊ธฐ ํŒŒ์‹ฑ const hugeData = JSON.parse(hugeJsonString); // ํƒ€์ž„์•„์›ƒ ์œ„ํ—˜

ํ•ด๊ฒฐ: ์ŠคํŠธ๋ฆฌ๋ฐ์ด๋ž‘ ์ฒญํฌ ์ฒ˜๋ฆฌ:

// โœ… Edge ์นœํ™”์  ํŒจํ„ด // ํฐ ์‘๋‹ต์€ ์ŠคํŠธ๋ฆฌ๋ฐ export async function GET() { const stream = new ReadableStream({ async start(controller) { for (let i = 0; i < 1000; i++) { const chunk = await fetchChunk(i); controller.enqueue(new TextEncoder().encode(JSON.stringify(chunk) + '\n')); // ๋‹ค๋ฅธ ์ž‘์—… ๋Œ์•„๊ฐˆ ํ‹ˆ ์ฃผ๊ธฐ await new Promise(resolve => setTimeout(resolve, 0)); } controller.close(); } }); return new Response(stream, { headers: { 'Content-Type': 'application/x-ndjson' } }); } // Web Streams๋กœ ํŒŒ์‹ฑ async function* parseJsonStream(response) { const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); for (const line of lines) { if (line.trim()) { yield JSON.parse(line); } } } }

์œ ํ˜• 5: ํฌ๊ธฐ ์ดˆ๊ณผ

Edge ํ•จ์ˆ˜๋Š” ํฌ๊ธฐ ์ œํ•œ์ด ํƒ€์ดํŠธํ•ฉ๋‹ˆ๋‹ค. Vercel: 14MB, Cloudflare Workers: 110MB:

// โŒ ๋ฒˆ๋“ค ์‚ฌ์ด์ฆˆ ํญํƒ„ import _ from 'lodash'; // 70KB+ minified import moment from 'moment'; // 300KB+ ๋กœ์ผ€์ผ ํฌํ•จ import * as AWS from 'aws-sdk'; // ์—„์ฒญ ํผ import 'core-js/stable'; // 150KB+ ํด๋ฆฌํ•„ // ์•„์ด์ฝ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ†ต์งธ๋กœ import * as Icons from '@heroicons/react/24/solid'; // ๋ฒˆ๋“ค์— ML ๋ชจ๋ธ์ด๋‚˜ ํฐ ๋ฐ์ดํ„ฐ ํฌํ•จ import model from './large-ml-model.json'; // 2MB ๋ชจ๋ธ

ํ•ด๊ฒฐ: ํŠธ๋ฆฌ์‰์ดํ‚น ๋นก์„ธ๊ฒŒ + ๋ ˆ์ด์ง€ ๋กœ๋“œ:

// โœ… ์‚ฌ์ด์ฆˆ ์ตœ์ ํ™” // lodash ์ „์ฒด ๋Œ€์‹  import groupBy from 'lodash/groupBy'; import debounce from 'lodash/debounce'; // moment ๋Œ€์‹  import { format, parseISO } from 'date-fns'; // aws-sdk v2 ๋Œ€์‹  import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'; // ์•„์ด์ฝ˜ ๊ฐœ๋ณ„ import import { HomeIcon } from '@heroicons/react/24/solid'; // ํฐ ๋ฐ์ดํ„ฐ๋Š” ๋Ÿฐํƒ€์ž„์— fetch export async function GET() { const model = await fetch('https://cdn.example.com/model.json').then(r => r.json()); // ... }

์‹ค์ „ ๋””๋ฒ„๊น… ์ผ€์ด์Šค

ํ”ํžˆ ๊ฒช๋Š” Edge Runtime ์‹คํŒจ ์ผ€์ด์Šค ๋ด…์‹œ๋‹ค.

์ผ€์ด์Šค 1: ๊ฐ‘์ž๊ธฐ "Module Not Found"

์ƒํ™ฉ: ๋กœ์ปฌ์—์„œ ์ž˜ ๋จ, ๋นŒ๋“œ๋„ ใ…‡ใ…‹, ๋ฐฐํฌํ•˜๋ฉด ๋Ÿฐํƒ€์ž„์—์„œ ํ„ฐ์ง.

Error: Cannot find module 'util'
    at EdgeRuntime (edge-runtime.js:1:1)

์›์ธ ๋ถ„์„:

// 1. ๋ญ๊ฐ€ 'util' ์“ฐ๋Š”์ง€ ์ฐพ๊ธฐ // ์˜์กด์„ฑ ํŠธ๋ฆฌ ๊นŒ๋ด์•ผ ํ•จ // package.json์€ ๋ฉ€์ฉกํ•ด ๋ณด์ž„ { "dependencies": { "next": "14.0.0", "jsonwebtoken": "9.0.0", // <-- ๋ฒ”์ธ! "next-auth": "4.24.0" } } // 2. ์˜์กด์„ฑ ํŠธ๋ฆฌ ํ™•์ธ // npm ls util ์น˜๋ฉด // ์ถœ๋ ฅ: // โ””โ”€โ”ฌ [email protected] // โ””โ”€โ”€ [email protected] // jsonwebtoken์ด ๋‚ด๋ถ€์—์„œ Node util ์”€!

ํ•ด๊ฒฐ:

// ๋ฐฉ๋ฒ• 1: Edge ํ˜ธํ™˜ JWT ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๊ต์ฒด import { SignJWT, jwtVerify } from 'jose'; // Edge ํ˜ธํ™˜! export const runtime = 'edge'; export async function POST(request) { const secret = new TextEncoder().encode(process.env.JWT_SECRET); // ์„œ๋ช… const token = await new SignJWT({ userId: '123' }) .setProtectedHeader({ alg: 'HS256' }) .setExpirationTime('1h') .sign(secret); // ๊ฒ€์ฆ const { payload } = await jwtVerify(token, secret); return Response.json({ token, payload }); } // ๋ฐฉ๋ฒ• 2: ๊ทธ๋ƒฅ ์ด ๋ผ์šฐํŠธ๋Š” Edge ์•ˆ ์“ฐ๊ธฐ // export const runtime = 'edge'; ๋นผ๋ฒ„๋ฆฌ๊ธฐ // Node.js ๋Ÿฐํƒ€์ž„์—์„œ ๋Œ๋ฆฌ๊ธฐ

์ผ€์ด์Šค 2: NextAuth.js ์„ธ์…˜ ํญ๋ฐœ

์ƒํ™ฉ: ๋กœ์ปฌ์—์„  ๋กœ๊ทธ์ธ ์ž˜ ๋จ, Edge ํ”„๋กœ๋•์…˜์—์„œ ํ„ฐ์ง.

Error: next-auth requires a secret to be set in production

๋˜๋Š”

Error: [next-auth]: `useSession` must be wrapped in a <SessionProvider />
// (๋ถ„๋ช… ๊ฐ์ŒŒ๋Š”๋ฐ? ์–ด์ œ๊นŒ์ง€ ๋๋Š”๋ฐ?)

์›์ธ:

// next-auth ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ Edge ์™„์ „ ํ˜ธํ™˜ ์•„๋‹˜ // app/api/auth/[...nextauth]/route.js import NextAuth from 'next-auth'; import GitHub from 'next-auth/providers/github'; export const runtime = 'edge'; // โŒ ์ด๊ฒŒ ๋ฌธ์ œ! export const { handlers, auth, signIn, signOut } = NextAuth({ providers: [GitHub], // DB ์–ด๋Œ‘ํ„ฐ๋„ Edge์—์„œ ์•ˆ ๋จ adapter: PrismaAdapter(prisma), // โŒ ๋„ค์ดํ‹ฐ๋ธŒ ์˜์กด์„ฑ! });

ํ•ด๊ฒฐ:

// app/api/auth/[...nextauth]/route.js import NextAuth from 'next-auth'; import GitHub from 'next-auth/providers/github'; // ๋ฐฉ๋ฒ• 1: ์ธ์ฆ์€ Node.js๋กœ (์ง€๊ธˆ ๊ธฐ์ค€ ๊ถŒ์žฅ) export const runtime = 'nodejs'; // โœ… ๋ช…์‹œ์  Node.js // ๋ฐฉ๋ฒ• 2: Edge ํ˜ธํ™˜ ์–ด๋Œ‘ํ„ฐ ์“ฐ๊ธฐ import { DrizzleAdapter } from '@auth/drizzle-adapter'; import { drizzle } from 'drizzle-orm/neon-http'; export const { handlers, auth } = NextAuth({ providers: [GitHub], adapter: DrizzleAdapter(drizzle(process.env.DATABASE_URL)), // โœ… Edge ํ˜ธํ™˜ }); // ๋ฏธ๋“ค์›จ์–ด์—์„  Edge ์จ๋„ ๋จ (์ฝ๊ธฐ๋งŒ, ์“ฐ๊ธฐ X) // middleware.js export { auth as middleware } from './auth'; export const config = { matcher: ['/dashboard/:path*'] };

์ผ€์ด์Šค 3: DB ์—ฐ๊ฒฐ ์ง€์˜ฅ

์ƒํ™ฉ: ๋กœ์ปฌ์—์„œ ์ฟผ๋ฆฌ ์ž˜ ๋จ, Edge ํ”„๋กœ๋•์…˜์—์„œ ํ„ฐ์ง.

Error: Connection pool exhausted
Error: prepared statement "s0" already exists
Error: Cannot establish database connection

์›์ธ:

// ์ผ๋ฐ˜์ ์ธ DB ์—ฐ๊ฒฐ์€ Edge์—์„œ ์•ˆ ๋จ // Edge ํ•จ์ˆ˜๋Š” ์ƒํƒœ๊ฐ€ ์—†๊ณ  ์ „์„ธ๊ณ„์— ํฉ์–ด์ ธ ์žˆ์Œ import { Pool } from 'pg'; // โŒ ์ปค๋„ฅ์…˜ ํ’€ Edge์—์„œ ์•ˆ ๋จ const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20, // Edge์—์„  ์˜๋ฏธ ์—†์Œ }); // Edge ํ˜ธ์ถœ๋งˆ๋‹ค ๊ฒฉ๋ฆฌ๋จ - ๊ณต์œ  ์ปค๋„ฅ์…˜ ํ’€ ์—†์Œ! // 300๊ตฐ๋ฐ+์—์„œ ๋™์‹œ์— ๋‹จ์ผ DB๋กœ ์—ฐ๊ฒฐ ์‹œ๋„ = ๋ง

ํ•ด๊ฒฐ:

// โœ… Edge ํ˜ธํ™˜ DB ํŒจํ„ด // ๋ฐฉ๋ฒ• 1: HTTP ๊ธฐ๋ฐ˜ DB ์—ฐ๊ฒฐ (Edge์— ์ตœ์ ) import { neon } from '@neondatabase/serverless'; export const runtime = 'edge'; export async function GET() { const sql = neon(process.env.DATABASE_URL); // ์ฟผ๋ฆฌ๋งˆ๋‹ค ๋ณ„๋„ HTTP ์š”์ฒญ - ์—ฐ๊ฒฐ ๊ด€๋ฆฌ ํ•„์š” ์—†์Œ const users = await sql`SELECT * FROM users LIMIT 10`; return Response.json({ users }); } // ๋ฐฉ๋ฒ• 2: Prisma Data Proxy import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient({ datasources: { db: { url: process.env.PRISMA_DATA_PROXY_URL, // ์ง์ ‘ ์—ฐ๊ฒฐ X, data proxy ๊ฒฝ์œ  }, }, }); // ๋ฐฉ๋ฒ• 3: ๋ฌด๊ฑฐ์šด DB ์ž‘์—…์€ ๊ทธ๋ƒฅ Edge ์•ˆ ์“ฐ๊ธฐ export const runtime = 'nodejs'; // ์ฝœ๋“œ ์Šคํƒ€ํŠธ ๊ฐ์ˆ˜ import { db } from '@/lib/database'; export async function GET() { const users = await db.user.findMany(); return Response.json({ users }); }

ํŒ๋‹จ ๊ธฐ์ค€: ์–ธ์ œ ๋ญ˜ ์“ธ๊นŒ

๋””๋ฒ„๊น… ๊ณ ํ†ต ๊ฒช๊ณ  ๋‚˜๋ฉด ๊ถ๊ธˆํ•ด์ง€์ฃ . ๋„๋Œ€์ฒด Edge๋Š” ์–ธ์ œ ์“ฐ๋Š” ๊ฑฐ์•ผ?

Edge ์“ธ ๋•Œ:

1. ๋ ˆ์ดํ„ด์‹œ ์ค‘์š” + ์บ์‹œ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ:

// โœ… Edge ์ตœ์ : ์ง€์—ญ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…, ์บ์‹œ๋œ ์ฝ˜ํ…์ธ  export const runtime = 'edge'; export async function GET(request) { const country = request.geo?.country || 'US'; const content = await fetch(`https://cdn.example.com/content/${country}.json`, { next: { revalidate: 3600 } }); return Response.json(await content.json()); }

2. ์ธ์ฆ ์ฒดํฌ (์ฝ๊ธฐ๋งŒ):

// โœ… Edge ์ตœ์ : JWT ๊ฒ€์ฆ, ๊ถŒํ•œ ํ™•์ธ export const runtime = 'edge'; export async function middleware(request) { const token = request.cookies.get('session'); if (!token) { return Response.redirect(new URL('/login', request.url)); } try { await jwtVerify(token.value, secret); return NextResponse.next(); } catch { return Response.redirect(new URL('/login', request.url)); } }

3. A/B ํ…Œ์ŠคํŠธ, ํ”ผ์ฒ˜ ํ”Œ๋ž˜๊ทธ:

// โœ… Edge ์ตœ์ : ์ฆ‰์‹œ ํŒ๋‹จ, ๋ฐฑ์—”๋“œ ํ˜ธ์ถœ ๋ถˆํ•„์š” export const runtime = 'edge'; export async function middleware(request) { const bucket = Math.random(); const variant = bucket < 0.5 ? 'control' : 'treatment'; const response = NextResponse.next(); response.cookies.set('experiment_variant', variant); return response; }

4. ๊ฐ„๋‹จํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ, ํ—ค๋” ์กฐ์ž‘:

// โœ… Edge ์ตœ์ : URL ๋ฆฌ๋ผ์ดํŒ…, ๋ณด์•ˆ ํ—ค๋” export const runtime = 'edge'; export async function middleware(request) { const url = request.nextUrl.clone(); if (url.pathname.startsWith('/old-blog/')) { url.pathname = url.pathname.replace('/old-blog/', '/blog/'); return Response.redirect(url); } const response = NextResponse.next(); response.headers.set('X-Frame-Options', 'DENY'); response.headers.set('X-Content-Type-Options', 'nosniff'); return response; }

Node.js ์“ธ ๋•Œ:

1. ์ปค๋„ฅ์…˜ ํ’€ ํ•„์š”ํ•œ DB ์ž‘์—…:

// โœ… Node.js: ์ „ํ†ต์  DB, ์ปค๋„ฅ์…˜ ์œ ์ง€ ํ•„์š” export const runtime = 'nodejs'; import { prisma } from '@/lib/prisma'; export async function GET() { const users = await prisma.user.findMany({ include: { posts: true }, take: 50, }); return Response.json({ users }); }

2. ํŒŒ์ผ ์ฒ˜๋ฆฌ, ๋ฐ”์ด๋„ˆ๋ฆฌ:

// โœ… Node.js: ํŒŒ์ผ์‹œ์Šคํ…œ, ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”์ด๋„ˆ๋ฆฌ export const runtime = 'nodejs'; import sharp from 'sharp'; import { writeFile } from 'fs/promises'; export async function POST(request) { const formData = await request.formData(); const file = formData.get('image'); const buffer = Buffer.from(await file.arrayBuffer()); const processed = await sharp(buffer) .resize(800, 600) .webp({ quality: 80 }) .toBuffer(); await writeFile(`./uploads/${file.name}.webp`, processed); return Response.json({ success: true }); }

3. ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง:

// โœ… Node.js: npm ์ƒํƒœ๊ณ„ ํ’€๋กœ ์“ธ ๋•Œ export const runtime = 'nodejs'; import Stripe from 'stripe'; import { sendEmail } from '@/lib/email'; import { generatePDF } from '@/lib/pdf'; import { prisma } from '@/lib/prisma'; export async function POST(request) { const order = await request.json(); const stripe = new Stripe(process.env.STRIPE_SECRET); const payment = await stripe.paymentIntents.create({...}); await prisma.order.update({...}); const pdf = await generatePDF(order); await sendEmail({ to: order.email, subject: '์ฃผ๋ฌธ ํ™•์ธ', attachments: [{ filename: 'invoice.pdf', content: pdf }], }); return Response.json({ success: true }); }

4. ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…:

// โœ… Node.js: 30์ดˆ ๋„˜๊ฒŒ ๊ฑธ๋ฆด ๋•Œ export const runtime = 'nodejs'; export const maxDuration = 300; // 5๋ถ„ export async function POST(request) { const { videoUrl } = await request.json(); const processed = await processVideo(videoUrl); // 2~3๋ถ„ ๊ฑธ๋ฆผ return Response.json({ downloadUrl: processed.url, duration: processed.duration }); }

ํ•˜์ด๋ธŒ๋ฆฌ๋“œ: ๋‘˜ ๋‹ค ์“ฐ๊ธฐ

์ •๋‹ต์€ "Edge๋ƒ Node.js๋ƒ"๊ฐ€ ์•„๋‹ˆ๋ผ ๋‘˜ ๋‹ค ์ „๋žต์ ์œผ๋กœ ์“ฐ๋Š” ๊ฒ๋‹ˆ๋‹ค:

src/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ api/
โ”‚   โ”‚   โ”œโ”€โ”€ auth/         # Node.js - DB ์„ธ์…˜
โ”‚   โ”‚   โ”œโ”€โ”€ payments/     # Node.js - Stripe, ๋ณต์žกํ•œ ๋กœ์ง
โ”‚   โ”‚   โ”œโ”€โ”€ upload/       # Node.js - ํŒŒ์ผ ์ฒ˜๋ฆฌ
โ”‚   โ”‚   โ”œโ”€โ”€ geo/          # Edge - ์œ„์น˜ ๊ธฐ๋ฐ˜
โ”‚   โ”‚   โ”œโ”€โ”€ flags/        # Edge - ํ”ผ์ฒ˜ ํ”Œ๋ž˜๊ทธ
โ”‚   โ”‚   โ””โ”€โ”€ health/       # Edge - ํ—ฌ์Šค์ฒดํฌ
โ”‚   โ””โ”€โ”€ middleware.ts     # Edge - ์ธ์ฆ, ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
// middleware.ts - ์ „์—ญ Edge import { auth } from './auth'; export default auth((request) => { if (!request.auth && request.nextUrl.pathname.startsWith('/dashboard')) { return Response.redirect(new URL('/login', request.url)); } }); export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], };
// app/api/geo/route.ts - ๋น ๋ฅธ ์ง€์—ญ ์กฐํšŒ๋Š” Edge export const runtime = 'edge'; export async function GET(request) { const { country, city, latitude, longitude } = request.geo || {}; const nearestStore = await fetch( `https://api.stores.com/nearest?lat=${latitude}&lng=${longitude}`, { next: { revalidate: 3600 } } ).then(r => r.json()); return Response.json({ nearestStore, userLocation: { country, city } }); }
// app/api/orders/route.ts - ๋ณต์žกํ•œ ๊ฑด Node.js export const runtime = 'nodejs'; export async function POST(request) { // ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ํ’€ํŒŒ์›Œ }

์ •๋ฆฌ

Edge Runtime์€ ์ข‹์€ ๊ธฐ์ˆ ์ด์ง€๋งŒ ๋งŒ๋Šฅ์€ ์•„๋‹™๋‹ˆ๋‹ค. Node.js ํ’€ ๊ธฐ๋Šฅ์„ ๊ธ€๋กœ๋ฒŒ ๋ฐฐํฌ + 10ms ์ฝœ๋“œ ์Šคํƒ€ํŠธ์™€ ๋งž๋ฐ”๊พผ ์ œ์•ฝ๋œ ํ™˜๊ฒฝ์ด์—์š”. ์ด ์ œ์•ฝโ€”๋ชจ๋“ˆ ์—†์Œ, ๋™์  ํ‰๊ฐ€ ๊ธˆ์ง€, ๋„ค์ดํ‹ฐ๋ธŒ ์•ˆ ๋จ, ํฌ๊ธฐ ์ œํ•œ, ์ƒํƒœ ์—†์Œโ€”์„ ์•Œ์•„์•ผ Edge ์„ฑ๊ณต์ ์œผ๋กœ ์”๋‹ˆ๋‹ค.

ํ•ต์‹ฌ:

  1. Edge๋Š” ๋น ๋ฅด๊ณ  ๋‹จ์ˆœํ•œ ์ž‘์—…์šฉ โ€” ์ธ์ฆ ์ฒดํฌ, ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ, A/B ํ…Œ์ŠคํŠธ, ์บ์‹œ ์ฝ˜ํ…์ธ . ๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์•„๋‹˜.

  2. ์• ๋งคํ•˜๋ฉด Node.js โ€” ์ฝœ๋“œ ์Šคํƒ€ํŠธ 250ms~1์ดˆ ๊ฐ์ˆ˜ํ•˜๊ณ  API ํ’€ ํ˜ธํ™˜์„ฑ ํ™•๋ณด.

  3. DB ์ „๋žต ์ค‘์š” โ€” Edge์—” HTTP ์—ฐ๊ฒฐ (Neon, PlanetScale HTTP, Prisma Data Proxy), Node.js์—” ์ปค๋„ฅ์…˜ ํ’€.

  4. ์˜์กด์„ฑ ๊นŒ๋ณด๊ธฐ โ€” npm ls ๋Œ๋ ค์„œ Node ์ฝ”์–ด ๋ชจ๋“ˆ ์“ฐ๋Š” ํŒจํ‚ค์ง€ ์ฐพ๊ธฐ. Edge ํ˜ธํ™˜์œผ๋กœ ๊ต์ฒด.

  5. ์‚ฌ์ด์ฆˆ ์‹ ๊ฒฝ์“ฐ๊ธฐ โ€” ํŠธ๋ฆฌ์‰์ดํ‚น ์—ด์‹ฌํžˆ, ๋ฐ์ดํ„ฐ ๋ ˆ์ด์ง€ ๋กœ๋“œ, ๋ฐฐ๋Ÿด ํŒŒ์ผ ๋Œ€์‹  ๊ตฌ์ฒด์  import.

  6. ํ•˜์ด๋ธŒ๋ฆฌ๋“œ๊ฐ€ ๋‹ต โ€” ๋ฏธ๋“ค์›จ์–ด๋ž‘ ๋ ˆ์ดํ„ด์‹œ ๋ฏผ๊ฐํ•œ ๋ผ์šฐํŠธ๋Š” Edge, ๋‚˜๋จธ์ง„ Node.js.

๋‹ค์Œ์— export const runtime = 'edge' ๋ถ™์ด๊ณ  ํ•จ์ˆ˜ ํ„ฐ์ง€๋ฉด, ์ด์ œ ์–ด๋”” ๋ด์•ผ ํ• ์ง€ ์•„์‹ค ๊ฒ๋‹ˆ๋‹ค. Edge Runtime์ด ๋ง๊ฐ€์ง„ ๊ฒŒ ์•„๋‹ˆ๋ผ ๊ทธ๋ƒฅ ๋‹ค๋ฅธ ๊ฑฐ์˜ˆ์š”. ๊ทธ ์ฐจ์ด ์•Œ๋ฉด ํ•จ์ • ์•ˆ ๋ฐŸ๊ณ  ์ž˜ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

edge-runtimenodejsserverlessnextjsvercelcloudflare-workersdebugging