๐Ÿš€ Vite ๋ฐฐํฌ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ: xdg-open, CJS API, npm ๋ฒ„์ „ ์ถฉ๋Œ ๋ฌธ์ œ ๋ถ„์„

@leekh8 ยท March 05, 2025 ยท 12 min read

Code N Solve ๐Ÿ“˜: Vite ๋ฐฐํฌ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ - xdg-open, CJS API, npm ๋ฒ„์ „ ์ถฉ๋Œ ๋ฌธ์ œ ๋ถ„์„

Vite ํ”„๋กœ์ ํŠธ๋ฅผ Render์—์„œ ๋ฐฐํฌํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ช‡ ๊ฐ€์ง€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

์ฒ˜์Œ์—๋Š” ๋ธŒ๋ผ์šฐ์ € ์ž๋™ ์‹คํ–‰ ๋ฌธ์ œ, ์ดํ›„์—๋Š” Vite์˜ CJS API ๊ฒฝ๊ณ , npm ๋ฒ„์ „ ์ถฉ๋Œ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค.

๊ฐ๊ฐ์˜ ์˜ค๋ฅ˜๋ฅผ ๋ถ„์„ํ•˜๊ณ  ํ•ด๊ฒฐํ•œ ๊ณผ์ •์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.


Vite๋ž€? โšก

Vite1๋Š” ํ”„๋ž‘์Šค์–ด๋กœ "๋น ๋ฅด๋‹ค"๋ฅผ ๋œปํ•˜๋Š” ์ด๋ฆ„ ๊ทธ๋Œ€๋กœ, ๊ธฐ์กด ๋ฒˆ๋“ค๋Ÿฌ๋“ค์˜ ๋А๋ฆฐ ๊ฐœ๋ฐœ ์„œ๋ฒ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ ์ฐจ์„ธ๋Œ€ ํ”„๋ก ํŠธ์—”๋“œ ๋นŒ๋“œ ๋„๊ตฌ๋‹ค. Evan You(Vue.js ์ฐฝ์‹œ์ž)๊ฐ€ ๊ฐœ๋ฐœํ–ˆ์œผ๋ฉฐ, Vue๋ฟ๋งŒ ์•„๋‹ˆ๋ผ React, Svelte, Vanilla JS ๋“ฑ ๋‹ค์–‘ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ง€์›ํ•œ๋‹ค.

Vite vs Webpack: ๋ฌด์—‡์ด ๋‹ค๋ฅธ๊ฐ€

๊ฐœ๋ฐœ ์„œ๋ฒ„ ๋ฐฉ์‹์˜ ์ฐจ์ด

Webpack์€ ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ ํ”„๋กœ์ ํŠธ ์ „์ฒด๋ฅผ ๋ฒˆ๋“ค๋งํ•œ ๋’ค ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ด ์ดˆ๊ธฐ ๋ฒˆ๋“ค๋ง ์‹œ๊ฐ„์ด ์ˆ˜์‹ญ ์ดˆ์—์„œ ์ˆ˜ ๋ถ„๊นŒ์ง€ ๋Š˜์–ด๋‚ฌ๋‹ค.

Webpack
์ „์ฒด ์†Œ์Šค ๋ถ„์„
์„œ๋ฒ„ ์‹œ์ž‘
์ „์ฒด ๋ฒˆ๋“ค๋ง
์„œ๋ฒ„ ์ค€๋น„ ์™„๋ฃŒ
๋ธŒ๋ผ์šฐ์ €์— ๋ฒˆ๋“ค ์ „๋‹ฌ

Vite๋Š” ์ „ํ˜€ ๋‹ค๋ฅธ ์ ‘๊ทผ์„ ์ทจํ•œ๋‹ค.

Vite
๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํŒŒ์ผ ์š”์ฒญ
์„œ๋ฒ„ ์‹œ์ž‘

์ฆ‰์‹œ
์š”์ฒญ๋œ ํŒŒ์ผ๋งŒ

๋ณ€ํ™˜ on-demand
ESM์œผ๋กœ ์ง์ ‘ ์ „๋‹ฌ

Vite๋Š” ๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ์ฆ‰์‹œ ์‹œ์ž‘ํ•˜๊ณ , ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํŠน์ • ๋ชจ๋“ˆ์„ ์š”์ฒญํ•  ๋•Œ๋งŒ ๊ทธ ํŒŒ์ผ์„ ๋ณ€ํ™˜ํ•œ๋‹ค. ์ด๊ฒƒ์ด ๊ฐ€๋Šฅํ•œ ์ด์œ ๋Š” ํ˜„๋Œ€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ **ESM(ECMAScript Module)**์„ ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

HMR (Hot Module Replacement) ์ฐจ์ด

ํŒŒ์ผ์„ ์ˆ˜์ •ํ•  ๋•Œ ํŽ˜์ด์ง€ ์ „์ฒด๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝ๋œ ๋ชจ๋“ˆ๋งŒ ๊ต์ฒดํ•˜๋Š” ๊ธฐ๋Šฅ์ด HMR์ด๋‹ค.

  • Webpack HMR: ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ๋ถ€ํ„ฐ ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ๊นŒ์ง€ ์ „์ฒด ์˜์กด์„ฑ ํŠธ๋ฆฌ๋ฅผ ๋‹ค์‹œ ๋ฒˆ๋“ค๋ง
  • Vite HMR: ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ ํ•˜๋‚˜๋งŒ ๋ธŒ๋ผ์šฐ์ €์— ์ง์ ‘ ์ „๋‹ฌ, ์˜์กด์„ฑ ํฌ๊ธฐ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ผ์ •ํ•œ ์†๋„ ์œ ์ง€

ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ

๊ฐœ๋ฐœ ์„œ๋ฒ„๋Š” ESM ๋ฐฉ์‹์ด์ง€๋งŒ, ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ๋Š” Rollup์„ ์‚ฌ์šฉํ•ด ์ตœ์ ํ™”๋œ ๋ฒˆ๋“ค์„ ์ƒ์„ฑํ•œ๋‹ค. Tree-shaking, ์ฝ”๋“œ ๋ถ„ํ• (Code Splitting), ์ž์‚ฐ ์ธ๋ผ์ด๋‹ ๋“ฑ์ด ์ž๋™์œผ๋กœ ์ ์šฉ๋œ๋‹ค.

ํ•ญ๋ชฉ Vite Webpack
๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ๊ฐ„ ์ˆ˜๋ฐฑ ms ์ˆ˜์‹ญ ์ดˆ~
HMR ์†๋„ ํŒŒ์ผ ํฌ๊ธฐ์™€ ๋ฌด๊ด€, ์ฆ‰๊ฐ ์˜์กด์„ฑ ํด์ˆ˜๋ก ๋А๋ ค์ง
์„ค์ • ๋ณต์žก๋„ ๋‚ฎ์Œ (๊ธฐ๋ณธ๊ฐ’ ์ตœ์ ํ™”) ๋†’์Œ
ํ”Œ๋Ÿฌ๊ทธ์ธ ์ƒํƒœ๊ณ„ Rollup ํ”Œ๋Ÿฌ๊ทธ์ธ ํ˜ธํ™˜ ๋ฐฉ๋Œ€ํ•œ Webpack ์ƒํƒœ๊ณ„
ํ”„๋กœ๋•์…˜ ๋ฒˆ๋“ค๋Ÿฌ Rollup Webpack
ESM ์ง€์› ๋„ค์ดํ‹ฐ๋ธŒ ๋ณ€ํ™˜ ํ•„์š”

Render.com์ด๋ž€? ๐ŸŒ

Render6๋Š” Heroku์˜ ๋Œ€์•ˆ์œผ๋กœ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ํด๋ผ์šฐ๋“œ ๋ฐฐํฌ ํ”Œ๋žซํผ์ด๋‹ค. GitHub ์ €์žฅ์†Œ์™€ ์—ฐ๊ฒฐํ•˜๋ฉด pushํ•  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

Render์˜ ์ฃผ์š” ํŠน์ง•

  • ๋ฌด๋ฃŒ ํ”Œ๋žœ: ์ •์  ์‚ฌ์ดํŠธ ๋ฌด๋ฃŒ, ์›น ์„œ๋น„์Šค๋Š” ์ผ์ • ์‹œ๊ฐ„ ํ›„ ์Šฌ๋ฆฝ ๋ชจ๋“œ
  • ์ž๋™ ๋ฐฐํฌ: GitHub/GitLab ์—ฐ๋™์œผ๋กœ push ์‹œ ์ž๋™ ๋ฐฐํฌ
  • Docker ์ง€์›: Dockerfile๋กœ ์™„์ „ํ•œ ํ™˜๊ฒฝ ์ œ์–ด ๊ฐ€๋Šฅ
  • ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ด€๋ฆฌ: ์›น ๋Œ€์‹œ๋ณด๋“œ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
  • ๋ฌด๋ฃŒ SSL: ์ž๋™ HTTPS ์ธ์ฆ์„œ ๋ฐœ๊ธ‰

Render์—์„œ Vite ํ”„๋กœ์ ํŠธ ๋ฐฐํฌ ํ๋ฆ„

GitHub์— push
Render ๋นŒ๋“œ ํŠธ๋ฆฌ๊ฑฐ
Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ

or npm install
๋นŒ๋“œ ๋ช…๋ น ์‹คํ–‰

npm run build
์‹œ์ž‘ ๋ช…๋ น ์‹คํ–‰

npm run preview
์™ธ๋ถ€ ์ ‘๊ทผ ๊ฐ€๋Šฅ

๋ฐฐํฌ ์™„๋ฃŒ


๐Ÿšจ ์˜ค๋ฅ˜ 1: xdg-open ์˜ค๋ฅ˜ โ€” "spawn xdg-open ENOENT"

๋ฌธ์ œ ์ƒํ™ฉ

Vite ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ ์‹œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

Error: spawn xdg-open ENOENT
    at ChildProcess._handle.onexit (node:internal/child_process:284:19)
    at onErrorNT (node:internal/child_process:477:16)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

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

xdg-open์€ Linux ํ™˜๊ฒฝ์—์„œ ๊ธฐ๋ณธ ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์—ฌ๋Š” ๋ช…๋ น์–ด๋‹ค. Render์˜ ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์—์„œ vite dev๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, Vite๊ฐ€ ์„œ๋ฒ„ ์‹œ์ž‘ ํ›„ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ž๋™์œผ๋กœ ์—ด๋ ค๊ณ  ์‹œ๋„ํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์„œ๋ฒ„ ์ปจํ…Œ์ด๋„ˆ์—๋Š” GUI ํ™˜๊ฒฝ์ด ์—†์œผ๋ฏ€๋กœ xdg-open์ด ์„ค์น˜๋˜์–ด ์žˆ์ง€ ์•Š๊ณ , ์‹คํ–‰๋„ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

์ฆ‰, Vite์˜ --open ์˜ต์…˜(๋˜๋Š” server.open: true ์„ค์ •)์ด ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—์„œ headless ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋ฐฉ๋ฒ• 1: ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋ธŒ๋ผ์šฐ์ € ์ž๋™ ์‹คํ–‰ ๋น„ํ™œ์„ฑํ™” (๊ถŒ์žฅ)

Render ๋Œ€์‹œ๋ณด๋“œ โ†’ Environment ํƒญ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ถ”๊ฐ€:

BROWSER=none

Vite๋Š” BROWSER ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ none์ด๋ฉด ๋ธŒ๋ผ์šฐ์ € ์ž๋™ ์‹คํ–‰์„ ๊ฑด๋„ˆ๋›ด๋‹ค.

๋ฐฉ๋ฒ• 2: vite.config.js์—์„œ server.open ๋น„ํ™œ์„ฑํ™”

import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    host: true,       // 0.0.0.0์œผ๋กœ ๋ฐ”์ธ๋”ฉ (์™ธ๋ถ€ ์ ‘๊ทผ ํ—ˆ์šฉ)
    port: 3000,
    open: false,      // ๋ธŒ๋ผ์šฐ์ € ์ž๋™ ์‹คํ–‰ ๋น„ํ™œ์„ฑํ™”
  },
})

๋ฐฉ๋ฒ• 3: xdg-utils ํŒจํ‚ค์ง€ ์„ค์น˜

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ xdg-open์ด ์—†์„ ๊ฒฝ์šฐ:

# Ubuntu/Debian
sudo apt update && sudo apt install xdg-utils

# CentOS/RHEL
sudo yum install xdg-utils

๋‹จ, Render ์ปจํ…Œ์ด๋„ˆ์—์„œ๋Š” ๋ฐฉ๋ฒ• 1์ด๋‚˜ 2๊ฐ€ ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜๋‹ค.2

์–ด๋–ค ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

  • Render/์„œ๋ฒ„ ๋ฐฐํฌ ํ™˜๊ฒฝ: ๋ฐฉ๋ฒ• 1 (ํ™˜๊ฒฝ ๋ณ€์ˆ˜) โ€” Dockerfile ์ˆ˜์ • ์—†์ด ๊ฐ„๋‹จ
  • ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๋งŒ: ๋ฐฉ๋ฒ• 2 (vite.config.js) โ€” ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ ์ œ์–ด
  • ๊ฐœ๋ฐœ ์„œ๋ฒ„์—์„œ ์‹ค์ œ๋กœ xdg-open ํ•„์š”: ๋ฐฉ๋ฒ• 3

๐Ÿšจ ์˜ค๋ฅ˜ 2: Vite CJS API ์‚ฌ์šฉ ๊ฒฝ๊ณ 

๋ฌธ์ œ ์ƒํ™ฉ

Vite ์‹คํ–‰ ์‹œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€๊ฐ€ ์ถœ๋ ฅ๋๋‹ค.

The CJS build of Vite's Node API is deprecated.
See https://vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.

๋นŒ๋“œ๊ฐ€ ์‹คํŒจํ•˜์ง€๋Š” ์•Š์ง€๋งŒ, ๋ฏธ๋ž˜ ๋ฒ„์ „์—์„œ๋Š” ์˜ค๋ฅ˜๋กœ ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ์–ด ๋ฌด์‹œํ•˜๋ฉด ์•ˆ ๋œ๋‹ค.

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

JavaScript ๋ชจ๋“ˆ ์‹œ์Šคํ…œ์—๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์ด ์žˆ๋‹ค.

CJS (CommonJS): Node.js์˜ ์ „ํ†ต์ ์ธ ๋ชจ๋“ˆ ๋ฐฉ์‹

// CJS ๋ฐฉ์‹
const vite = require('vite')
module.exports = { ... }

ESM (ECMAScript Module): ํ˜„๋Œ€ JavaScript ํ‘œ์ค€

// ESM ๋ฐฉ์‹
import { defineConfig } from 'vite'
export default defineConfig({ ... })

Vite 5 ๋ฒ„์ „๋ถ€ํ„ฐ๋Š” CJS ๋ฐฉ์‹์˜ Node API ์ง€์›์„ deprecated ์ฒ˜๋ฆฌํ–ˆ๋‹ค.3 ๊ธฐ์กด์˜ vite.config.js๊ฐ€ CJS ๋ฐฉ์‹์„ ๋”ฐ๋ฅด๊ณ  ์žˆ์œผ๋ฉด ์ด ๊ฒฝ๊ณ ๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค.

Node.js๊ฐ€ .js ํŒŒ์ผ์„ CJS๋กœ ์ฒ˜๋ฆฌํ• ์ง€ ESM์œผ๋กœ ์ฒ˜๋ฆฌํ• ์ง€๋Š”:

  • package.json์˜ "type" ํ•„๋“œ๋กœ ๊ฒฐ์ •
  • "type": "module" โ†’ .js ํŒŒ์ผ์„ ESM์œผ๋กœ ์ฒ˜๋ฆฌ
  • "type": "commonjs" ๋˜๋Š” ํ•„๋“œ ์—†์Œ โ†’ .js ํŒŒ์ผ์„ CJS๋กœ ์ฒ˜๋ฆฌ
  • .mjs ํ™•์žฅ์ž โ†’ ํ•ญ์ƒ ESM, .cjs ํ™•์žฅ์ž โ†’ ํ•ญ์ƒ CJS

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋ฐฉ๋ฒ• 1: vite.config.js๋ฅผ vite.config.mjs๋กœ ๋ณ€๊ฒฝ

mv vite.config.js vite.config.mjs

ํŒŒ์ผ ์ด๋ฆ„๋งŒ ๋ฐ”๊ฟ”๋„ Node.js๊ฐ€ ESM์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ ๊ฒฝ๊ณ ๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค. ๊ธฐ์กด require/module.exports ๊ตฌ๋ฌธ์ด ์žˆ๋‹ค๋ฉด import/export default๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค.

// vite.config.mjs
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    host: true,
    port: 3000,
    open: false,
  },
})

๋ฐฉ๋ฒ• 2: package.json์— "type": "module" ์ถ”๊ฐ€

{
  "name": "my-vite-app",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ”„๋กœ์ ํŠธ ์ „์ฒด์˜ .js ํŒŒ์ผ์ด ESM์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค. ๋‹จ, ์ด ๊ฒฝ์šฐ CJS ๋ฐฉ์‹์˜ ๋‹ค๋ฅธ ์„ค์ • ํŒŒ์ผ๋“ค(์˜ˆ: jest.config.js)๋„ ์˜ํ–ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฃผ์˜ํ•œ๋‹ค.

๋ฐฉ๋ฒ• 3: require ๊ตฌ๋ฌธ์„ import๋กœ ๋ณ€๊ฒฝ

๊ธฐ์กด vite.config.js ๋‚ด์šฉ์ด CJS ๋ฐฉ์‹์ด๋ผ๋ฉด:

// ๋ณ€๊ฒฝ ์ „ (CJS)
const { defineConfig } = require('vite')
const react = require('@vitejs/plugin-react')

module.exports = defineConfig({
  plugins: [react()],
})
// ๋ณ€๊ฒฝ ํ›„ (ESM)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})

์–ด๋–ค ๋ฐฉ๋ฒ•์ด ์ ํ•ฉํ•œ๊ฐ€?

์ƒํ™ฉ ๊ถŒ์žฅ ๋ฐฉ๋ฒ•
Vite ์„ค์ •๋งŒ ESM์œผ๋กœ ๋ฐ”๊พธ๊ณ  ์‹ถ๋‹ค ๋ฐฉ๋ฒ• 1 (.mjs ํ™•์žฅ์ž)
ํ”„๋กœ์ ํŠธ ์ „์ฒด๋ฅผ ESM์œผ๋กœ ์ „ํ™˜ํ•˜๊ณ  ์‹ถ๋‹ค ๋ฐฉ๋ฒ• 2 ("type": "module")
.mjs ํ™•์žฅ์ž๋ฅผ ์“ฐ๊ธฐ ์‹ซ๋‹ค ๋ฐฉ๋ฒ• 2 + import ๊ตฌ๋ฌธ ํ†ต์ผ

๐Ÿšจ ์˜ค๋ฅ˜ 3: npm ๋ฒ„์ „ ์ถฉ๋Œ โ€” "EBADENGINE" ์˜ค๋ฅ˜

๋ฌธ์ œ ์ƒํ™ฉ

Docker ๋นŒ๋“œ ๊ณผ์ •์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

npm error code EBADENGINE
npm error engine Unsupported engine
npm error engine Not compatible with your version of node/npm: npm@11.1.0
npm error notsup Required: {"node":"^20.17.0 || >=22.9.0"}
npm error notsup Actual:   {"npm":"10.8.2","node":"v18.20.7"}

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

npm@11.1.0์€ Node.js 20.17.0 ์ด์ƒ ๋˜๋Š” 22.9.0 ์ด์ƒ์ด ํ•„์š”ํ•˜๋‹ค.4 ๊ทธ๋Ÿฐ๋ฐ Dockerfile์—์„œ node:18์„ ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, Node.js ๋ฒ„์ „์ด 18.x์ด๋ฏ€๋กœ npm 11.x๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์—†๋‹ค.

์ด ์˜ค๋ฅ˜๋Š” ์ฃผ๋กœ package.json์˜ postinstall ์Šคํฌ๋ฆฝํŠธ์— npm install -g npm@latest๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์„ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.

{
  "scripts": {
    "postinstall": "npm install -g npm@latest"  // โ† ์ด ๋ถ€๋ถ„์ด ๋ฌธ์ œ
  }
}

npm@latest๊ฐ€ 11.x์ธ๋ฐ Node.js 18๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค.5

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋ฐฉ๋ฒ• 1: Node.js ๋ฒ„์ „์„ 20 ์ด์ƒ์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ (๊ถŒ์žฅ)

# ๋ณ€๊ฒฝ ์ „
FROM node:18-alpine

# ๋ณ€๊ฒฝ ํ›„
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --legacy-peer-deps
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

LTS(Long Term Support) ๋ฒ„์ „์ธ Node.js 20์„ ์‚ฌ์šฉํ•˜๋ฉด npm 10.x, 11.x ๋ชจ๋‘ ํ˜ธํ™˜๋œ๋‹ค. node:20-alpine์€ Alpine Linux ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๊ฐ€ ์ž‘์•„ ๊ถŒ์žฅ๋œ๋‹ค.

๋ฐฉ๋ฒ• 2: postinstall ์Šคํฌ๋ฆฝํŠธ ์ˆ˜์ •

npm์„ ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ๊ฐ•์ œ ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.

{
  "scripts": {
    "postinstall": "node -v && npm -v"
  }
}

๋˜๋Š” ํŠน์ • ๋ฒ„์ „์œผ๋กœ ๊ณ ์ •:

{
  "scripts": {
    "postinstall": "npm install -g npm@10.8.2"
  }
}

๋ฐฉ๋ฒ• 3: engines ํ•„๋“œ๋กœ ๋ฒ„์ „ ์ œ์•ฝ ๋ช…์‹œ

package.json์— ์ง€์›ํ•˜๋Š” Node.js ๋ฒ„์ „ ๋ฒ”์œ„๋ฅผ ๋ช…์‹œํ•œ๋‹ค.

{
  "engines": {
    "node": ">=20.0.0",
    "npm": ">=10.0.0"
  }
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ ์‹œ๋„ ์‹œ ๋ช…ํ™•ํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.


๐Ÿšจ ์˜ค๋ฅ˜ 4: process.env not defined in browser

๋ฌธ์ œ ์ƒํ™ฉ

Vite๋กœ ๋นŒ๋“œํ•œ ์•ฑ์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

Uncaught ReferenceError: process is not defined
    at App.jsx:5

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

process.env๋Š” Node.js ๋Ÿฐํƒ€์ž„ ํ™˜๊ฒฝ์—์„œ๋งŒ ์กด์žฌํ•˜๋Š” ๊ฐ์ฒด๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—๋Š” process๋ผ๋Š” ์ „์—ญ ๋ณ€์ˆ˜๊ฐ€ ์—†๋‹ค. CRA(Create React App)๋Š” webpack์ด process.env๋ฅผ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ๋™์ž‘ํ•˜๋„๋ก ํด๋ฆฌํ•„์„ ๋„ฃ์–ด์คฌ์ง€๋งŒ, Vite๋Š” ์ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

Vite์—์„œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— import.meta.env๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

// ์ž˜๋ชป๋œ ๋ฐฉ๋ฒ• (process.env)
const apiUrl = process.env.REACT_APP_API_URL

// ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ• (import.meta.env)
const apiUrl = import.meta.env.VITE_API_URL

Vite์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” VITE_ ์ ‘๋‘์‚ฌ๊ฐ€ ์žˆ์–ด์•ผ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์— ๋…ธ์ถœ๋œ๋‹ค.

# .env ํŒŒ์ผ
VITE_API_URL=https://api.example.com
VITE_APP_NAME=MyApp
SECRET_KEY=this_will_not_be_exposed  # VITE_ ์ ‘๋‘์‚ฌ ์—†์œผ๋ฉด ๋…ธ์ถœ ์•ˆ ๋จ
// ์‚ฌ์šฉ ์˜ˆ์‹œ
console.log(import.meta.env.VITE_API_URL)   // "https://api.example.com"
console.log(import.meta.env.VITE_APP_NAME)  // "MyApp"
console.log(import.meta.env.SECRET_KEY)     // undefined (์•ˆ์ „ํ•˜๊ฒŒ ๋ณดํ˜ธ๋จ)
console.log(import.meta.env.MODE)           // "development" ๋˜๋Š” "production"
console.log(import.meta.env.DEV)            // true (๊ฐœ๋ฐœ ๋ชจ๋“œ์—์„œ)
console.log(import.meta.env.PROD)           // true (ํ”„๋กœ๋•์…˜์—์„œ)

๐Ÿšจ ์˜ค๋ฅ˜ 5: ์ ˆ๋Œ€ ๊ฒฝ๋กœ alias ์„ค์ • ์˜ค๋ฅ˜

๋ฌธ์ œ ์ƒํ™ฉ

// ์ด๋Ÿฐ import๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Œ
import Button from '@/components/Button'
// Error: Failed to resolve import "@/components/Button"

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

Vite๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ @ ๊ฐ™์€ ๊ฒฝ๋กœ ๋ณ„์นญ์„ ๋ชจ๋ฅธ๋‹ค. Webpack์—์„œ๋Š” resolve.alias๋กœ ์„ค์ •ํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ, Vite๋„ ๋™์ผํ•˜๊ฒŒ ์„ค์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

vite.config.js์—์„œ alias ์„ค์ •์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@hooks': path.resolve(__dirname, './src/hooks'),
      '@utils': path.resolve(__dirname, './src/utils'),
    },
  },
})

TypeScript ํ”„๋กœ์ ํŠธ๋ผ๋ฉด tsconfig.json์—๋„ ๋™์ผํ•˜๊ฒŒ ์„ค์ •ํ•ด์•ผ IDE ์ž๋™์™„์„ฑ์ด ๋™์ž‘ํ•œ๋‹ค.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@hooks/*": ["src/hooks/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}

๐Ÿšจ ์˜ค๋ฅ˜ 6: ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฌผ ๊ฒฝ๋กœ ์„ค์ • โ€” base URL ์˜ค๋ฅ˜

๋ฌธ์ œ ์ƒํ™ฉ

GitHub Pages๋‚˜ ์„œ๋ธŒ ๊ฒฝ๋กœ์— ๋ฐฐํฌํ•  ๋•Œ CSS/JS ํŒŒ์ผ์„ ์ฐพ์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ.

# ๋ฐฐํฌ URL: https://username.github.io/my-repo/
# ํ•˜์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋‹ค์Œ์„ ์š”์ฒญ:
GET https://username.github.io/assets/index-abc123.js  # ์ž˜๋ชป๋œ ๊ฒฝ๋กœ
# ์˜ฌ๋ฐ”๋ฅธ ๊ฒฝ๋กœ:
GET https://username.github.io/my-repo/assets/index-abc123.js

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

Vite๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋นŒ๋“œ๋œ ํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋ฅผ /(๋ฃจํŠธ)๋กœ ์„ค์ •ํ•œ๋‹ค. ์„œ๋ธŒ ๊ฒฝ๋กœ์— ๋ฐฐํฌํ•˜๋ฉด ์ •์  ์ž์‚ฐ์„ ์ฐพ์ง€ ๋ชปํ•œ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

vite.config.js์˜ base ์˜ต์…˜์„ ์„ค์ •ํ•œ๋‹ค.

import { defineConfig } from 'vite'

export default defineConfig({
  // GitHub Pages์˜ ๊ฒฝ์šฐ
  base: '/my-repo/',

  // ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์œ ์—ฐํ•˜๊ฒŒ ์„ค์ •
  base: process.env.VITE_BASE_URL || '/',

  build: {
    outDir: 'dist',        // ๋นŒ๋“œ ์ถœ๋ ฅ ๋””๋ ‰ํ„ฐ๋ฆฌ
    assetsDir: 'assets',   // ์ •์  ์ž์‚ฐ ๋””๋ ‰ํ„ฐ๋ฆฌ
    sourcemap: false,      // ํ”„๋กœ๋•์…˜์—์„œ ์†Œ์Šค๋งต ๋น„ํ™œ์„ฑํ™”
  },
})

Render.com์— ๋ฐฐํฌํ•  ๋•Œ๋Š” ๋ฃจํŠธ ๊ฒฝ๋กœ(/)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์œผ๋ฏ€๋กœ ๊ธฐ๋ณธ๊ฐ’ ๊ทธ๋Œ€๋กœ ๋‘๋ฉด ๋œ๋‹ค.


Vite ์„ค์ • ํŒŒ์ผ ์™„์ „ ์ •๋ฆฌ

์‹ค๋ฌด์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” vite.config.js ์˜ต์…˜๋“ค์„ ์ •๋ฆฌํ–ˆ๋‹ค.

import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig(({ command, mode }) => {
  // ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ (๋ชจ๋“  VITE_ ์ ‘๋‘์‚ฌ ๋ณ€์ˆ˜)
  const env = loadEnv(mode, process.cwd(), '')

  return {
    // ํ”Œ๋Ÿฌ๊ทธ์ธ
    plugins: [react()],

    // ๊ฒฝ๋กœ ๋ณ„์นญ
    resolve: {
      alias: {
        '@': path.resolve(__dirname, './src'),
      },
    },

    // ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์„ค์ •
    server: {
      host: '0.0.0.0',   // ์™ธ๋ถ€ ์ ‘๊ทผ ํ—ˆ์šฉ (Docker, ๋„คํŠธ์›Œํฌ ์ ‘๊ทผ)
      port: 3000,
      open: false,        // ๋ธŒ๋ผ์šฐ์ € ์ž๋™ ์‹คํ–‰ ๋น„ํ™œ์„ฑํ™”
      proxy: {
        // API ์š”์ฒญ์„ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋กœ ํ”„๋ก์‹œ
        '/api': {
          target: env.VITE_API_URL || 'http://localhost:8080',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },

    // ๋นŒ๋“œ ์„ค์ •
    build: {
      outDir: 'dist',
      assetsDir: 'assets',
      sourcemap: mode !== 'production',
      minify: 'esbuild',
      rollupOptions: {
        output: {
          // ์ฒญํฌ ๋ถ„ํ•  ์ „๋žต
          manualChunks: {
            vendor: ['react', 'react-dom'],
            router: ['react-router-dom'],
          },
        },
      },
    },

    // ๊ธฐ๋ณธ ๊ฒฝ๋กœ
    base: env.VITE_BASE_URL || '/',

    // ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ ‘๋‘์‚ฌ (๊ธฐ๋ณธ: VITE_)
    envPrefix: 'VITE_',

    // ํ”„๋ฆฌ๋ทฐ ์„œ๋ฒ„ (vite preview ๋ช…๋ น)
    preview: {
      host: '0.0.0.0',
      port: 4173,
    },
  }
})

Render ๋ฐฐํฌ ์„ค์ •

1. ์›น ์„œ๋น„์Šค ์„ค์ • (Build & Deploy)

Render ๋Œ€์‹œ๋ณด๋“œ์—์„œ ์ƒˆ Web Service๋ฅผ ์ƒ์„ฑํ•  ๋•Œ:

ํ•ญ๋ชฉ ๊ฐ’
Runtime Node
Build Command npm ci && npm run build
Start Command npm run preview -- --host 0.0.0.0 --port $PORT

$PORT๋Š” Render๊ฐ€ ์ž๋™์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ํฌํŠธ ๋ฒˆํ˜ธ๋‹ค. Render๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ 10000๋ฒˆ ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, vite preview๊ฐ€ ํ•ด๋‹น ํฌํŠธ์— ๋ฐ”์ธ๋”ฉ๋˜๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.

2. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •

Render ๋Œ€์‹œ๋ณด๋“œ โ†’ Environment ํƒญ:

NODE_ENV=production
BROWSER=none
VITE_API_URL=https://api.your-backend.com
VITE_BASE_URL=/

3. package.json ์Šคํฌ๋ฆฝํŠธ ์ •๋ฆฌ

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "preview:render": "vite preview --host 0.0.0.0"
  }
}

Docker + Vite + Render ์ „์ฒด ์„ค์ • ์˜ˆ์ œ

Dockerfile (๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ)

# === ๋นŒ๋“œ ์Šคํ…Œ์ด์ง€ ===
FROM node:20-alpine AS builder

WORKDIR /app

# ์˜์กด์„ฑ ์„ค์น˜ (์บ์‹œ ํ™œ์šฉ)
COPY package*.json ./
RUN npm ci

# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ ๋ฐ ๋นŒ๋“œ
COPY . .
RUN npm run build

# === ํ”„๋กœ๋•์…˜ ์Šคํ…Œ์ด์ง€ ===
FROM node:20-alpine AS production

WORKDIR /app

# ๋นŒ๋“œ ๊ฒฐ๊ณผ๋ฌผ๋งŒ ๋ณต์‚ฌ
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./

# preview ์„œ๋ฒ„ ์‹คํ–‰์— ํ•„์š”ํ•œ vite๋งŒ ์„ค์น˜
RUN npm ci --only=production

# ํฌํŠธ ๋…ธ์ถœ
EXPOSE 4173

# vite preview๋กœ ์ •์  ํŒŒ์ผ ์„œ๋น™
CMD ["npx", "vite", "preview", "--host", "0.0.0.0", "--port", "4173"]

.dockerignore

node_modules
dist
.env
.env.local
.git
*.log

docker-compose.yml (๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ)

version: '3.8'

services:
  app:
    build:
      context: .
      target: production
    ports:
      - "4173:4173"
    environment:
      - NODE_ENV=production
      - BROWSER=none
      - VITE_API_URL=http://localhost:8080

๊ฐœ๋ฐœ/์Šคํ…Œ์ด์ง•/ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ๋ถ„๋ฆฌ

Vite๋Š” .env ํŒŒ์ผ์„ ์—ฌ๋Ÿฌ ๊ฐœ ์ง€์›ํ•œ๋‹ค.

.env                # ๋ชจ๋“  ํ™˜๊ฒฝ์—์„œ ๋กœ๋“œ (๊ณตํ†ต)
.env.local          # ๋ชจ๋“  ํ™˜๊ฒฝ์—์„œ ๋กœ๋“œ, git ๋ฌด์‹œ (๋กœ์ปฌ override)
.env.development    # ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ๋กœ๋“œ (vite dev)
.env.staging        # ์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ์—์„œ๋งŒ ๋กœ๋“œ (์ปค์Šคํ…€ mode)
.env.production     # ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋งŒ ๋กœ๋“œ (vite build)

.env (๊ณตํ†ต)

VITE_APP_NAME=MyApp
VITE_VERSION=1.0.0

.env.development

VITE_API_URL=http://localhost:8080
VITE_DEBUG=true

.env.staging

VITE_API_URL=https://staging-api.example.com
VITE_DEBUG=true

.env.production

VITE_API_URL=https://api.example.com
VITE_DEBUG=false

๋นŒ๋“œ ์‹œ mode๋ฅผ ์ง€์ •ํ•ด ํ™˜๊ฒฝ์„ ์„ ํƒํ•œ๋‹ค.

# ๊ฐœ๋ฐœ ์„œ๋ฒ„
npm run dev  # .env.development ์‚ฌ์šฉ

# ์Šคํ…Œ์ด์ง• ๋นŒ๋“œ
vite build --mode staging  # .env.staging ์‚ฌ์šฉ

# ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ
npm run build  # .env.production ์‚ฌ์šฉ (๊ธฐ๋ณธ)

๊ฒฐ๋ก 

Vite์™€ Render๋ฅผ ์กฐํ•ฉํ•œ ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์š” ์˜ค๋ฅ˜๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ–ˆ๋‹ค.

ํ•ต์‹ฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  1. xdg-open ์˜ค๋ฅ˜: Render ํ™˜๊ฒฝ ๋ณ€์ˆ˜์— BROWSER=none ์ถ”๊ฐ€
  2. CJS API ๊ฒฝ๊ณ : package.json์— "type": "module" ์ถ”๊ฐ€ ๋˜๋Š” .mjs ํ™•์žฅ์ž ์‚ฌ์šฉ
  3. npm ๋ฒ„์ „ ์ถฉ๋Œ: Dockerfile์—์„œ Node.js 20 ์ด์ƒ ์‚ฌ์šฉ
  4. process.env ์˜ค๋ฅ˜: import.meta.env.VITE_* ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜
  5. ๊ฒฝ๋กœ alias ์˜ค๋ฅ˜: vite.config.js์˜ resolve.alias ์„ค์ •
  6. base URL ์˜ค๋ฅ˜: ์„œ๋ธŒ ๊ฒฝ๋กœ ๋ฐฐํฌ ์‹œ base ์˜ต์…˜ ์„ค์ •
  7. ํ™˜๊ฒฝ ๋ณ€์ˆ˜: VITE_ ์ ‘๋‘์‚ฌ ๊ทœ์น™ ์ค€์ˆ˜ + .env.* ํŒŒ์ผ ๋ถ„๋ฆฌ

Vite์˜ ๋น ๋ฅธ ๊ฐœ๋ฐœ ๊ฒฝํ—˜๊ณผ Render์˜ ๊ฐ„ํŽธํ•œ ๋ฐฐํฌ๋ฅผ ์กฐํ•ฉํ•˜๋ฉด ํ”„๋กœ๋•์…˜ ์ˆ˜์ค€์˜ ์›น ์•ฑ์„ ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•˜๊ณ  ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋‹ค.

@leekh8
๋ณด์•ˆ, ์›น ๊ฐœ๋ฐœ, Python์„ ๋‹ค๋ฃจ๋Š” ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ