Skip to content

Commit 589cb20

Browse files
docs(blog): 검색 유입용 기술 팁 포스트 1차 배치 (5개, 3개 언어)
- JavaScript UTC 한국시간(KST) 변환 함수 - Next.js Prisma 싱글톤 패턴 (핫리로드 연결 누수 해결) - Docker 멀티스테이지 빌드 Next.js 최적화 - Docker Compose YAML 앵커(&)로 반복 설정 제거 - Node.js Redis 싱글톤 연결 + 지수 백오프 재연결
1 parent 3e181cb commit 589cb20

15 files changed

Lines changed: 759 additions & 0 deletions
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
layout: post
3+
title: "JavaScript UTC to KST Conversion - 3 Utility Functions for Production"
4+
date: 2025-01-10 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [JavaScript, UTC, KST, timezone, Date]
7+
author: "Kevin Park"
8+
lang: en
9+
excerpt: "Convert between UTC and Korean Standard Time (KST) in JavaScript without external libraries."
10+
---
11+
12+
## Problem
13+
14+
Your backend stores timestamps in UTC, but the frontend needs to display Korean time (KST, UTC+9). `toLocaleString` behavior varies across browsers, so manual conversion is safer.
15+
16+
## Solution
17+
18+
```javascript
19+
// 1. Korea date string → UTC date
20+
function toUtcDate(koreaDateStr) {
21+
const date = new Date(koreaDateStr + 'T00:00:00+09:00');
22+
return date.toISOString().split('T')[0];
23+
}
24+
25+
// 2. UTC timestamp → Korea date
26+
function toKoreaDate(utcTimestamp) {
27+
const date = new Date(utcTimestamp);
28+
if (isNaN(date.getTime())) return '';
29+
const koreaTime = new Date(date.getTime() + 9 * 60 * 60 * 1000);
30+
return koreaTime.toISOString().split('T')[0];
31+
}
32+
33+
// 3. UTC date range for a single Korea day (for API queries)
34+
function getUtcDateRange(koreaDateStr) {
35+
const startUtc = toUtcDate(koreaDateStr);
36+
const nextDay = new Date(koreaDateStr + 'T00:00:00+09:00');
37+
nextDay.setDate(nextDay.getDate() + 1);
38+
const endUtc = toUtcDate(nextDay.toISOString().split('T')[0]);
39+
return [startUtc, endUtc];
40+
}
41+
```
42+
43+
## Key Points
44+
45+
- Midnight in Korea (00:00 KST) is 15:00 UTC the previous day. When querying by date, you need a 2-day UTC range to avoid missing data.
46+
- Appending `+09:00` offset directly during parsing eliminates the need for timezone libraries.
47+
- Adding `9 * 60 * 60 * 1000` (32,400,000ms) is simple and works perfectly for Korea since it doesn't observe DST.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
layout: post
3+
title: "JavaScript UTC・韓国時間(KST)変換 - 実務で使える3つの関数"
4+
date: 2025-01-10 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [JavaScript, UTC, KST, timezone, Date]
7+
author: "Kevin Park"
8+
lang: ja
9+
excerpt: "JavaScriptでUTCと韓国標準時(KST)を外部ライブラリなしで変換する方法をご紹介します。"
10+
---
11+
12+
## 問題
13+
14+
バックエンドはUTCで保存しているのに、フロントエンドでは韓国時間で表示しなければなりません。`toLocaleString`はブラウザによって結果が異なる場合があるため、直接変換する方が安全です。
15+
16+
## 解決方法
17+
18+
```javascript
19+
// 1. 韓国日付文字列 → UTC日付
20+
function toUtcDate(koreaDateStr) {
21+
const date = new Date(koreaDateStr + 'T00:00:00+09:00');
22+
return date.toISOString().split('T')[0];
23+
}
24+
25+
// 2. UTCタイムスタンプ → 韓国日付
26+
function toKoreaDate(utcTimestamp) {
27+
const date = new Date(utcTimestamp);
28+
if (isNaN(date.getTime())) return '';
29+
const koreaTime = new Date(date.getTime() + 9 * 60 * 60 * 1000);
30+
return koreaTime.toISOString().split('T')[0];
31+
}
32+
33+
// 3. 韓国の1日分のUTC範囲(APIクエリ用)
34+
function getUtcDateRange(koreaDateStr) {
35+
const startUtc = toUtcDate(koreaDateStr);
36+
const nextDay = new Date(koreaDateStr + 'T00:00:00+09:00');
37+
nextDay.setDate(nextDay.getDate() + 1);
38+
const endUtc = toUtcDate(nextDay.toISOString().split('T')[0]);
39+
return [startUtc, endUtc];
40+
}
41+
```
42+
43+
## ポイント
44+
45+
- 韓国の深夜0時(00:00 KST)は前日の15:00 UTCです。日付基準でクエリする際は、UTCの範囲を2日分に設定しないとデータが欠落します。
46+
- パース時に`+09:00`オフセットを直接付与することで、タイムゾーンライブラリなしでも正確に変換できます。
47+
- `9 * 60 * 60 * 1000`(32,400,000ms)を加算する方法は、サマータイムのない韓国では完璧に動作します。
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
layout: post
3+
title: "JavaScript UTC 한국시간(KST) 변환 - 실무에서 자주 쓰는 3가지 함수"
4+
date: 2025-01-10 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [JavaScript, UTC, KST, timezone, Date]
7+
author: "Kevin Park"
8+
lang: ko
9+
excerpt: "JavaScript에서 UTC와 한국시간(KST) 사이를 변환하는 실무 코드. toLocaleString 없이 직접 변환하는 방법."
10+
---
11+
12+
## 문제
13+
14+
백엔드는 UTC로 저장하는데, 프론트엔드에서는 한국시간으로 보여줘야 한다. `toLocaleString`은 브라우저마다 결과가 다를 수 있어서 직접 변환하는 게 안전하다.
15+
16+
## 해결
17+
18+
```javascript
19+
// 1. 한국 날짜 문자열 → UTC 날짜
20+
function toUtcDate(koreaDateStr) {
21+
const date = new Date(koreaDateStr + 'T00:00:00+09:00');
22+
return date.toISOString().split('T')[0];
23+
}
24+
25+
// 2. UTC 타임스탬프 → 한국 날짜
26+
function toKoreaDate(utcTimestamp) {
27+
const date = new Date(utcTimestamp);
28+
if (isNaN(date.getTime())) return '';
29+
const koreaTime = new Date(date.getTime() + 9 * 60 * 60 * 1000);
30+
return koreaTime.toISOString().split('T')[0];
31+
}
32+
33+
// 3. 한국 날짜 하루의 UTC 범위 (API 조회용)
34+
function getUtcDateRange(koreaDateStr) {
35+
const startUtc = toUtcDate(koreaDateStr);
36+
const nextDay = new Date(koreaDateStr + 'T00:00:00+09:00');
37+
nextDay.setDate(nextDay.getDate() + 1);
38+
const endUtc = toUtcDate(nextDay.toISOString().split('T')[0]);
39+
return [startUtc, endUtc];
40+
}
41+
```
42+
43+
## 핵심 포인트
44+
45+
- 한국 자정(00:00 KST)은 전날 15:00 UTC다. 그래서 날짜 기준 조회할 때 UTC 범위를 2일치로 잡아야 데이터가 빠지지 않는다.
46+
- `+09:00` 오프셋을 직접 붙여서 파싱하면 타임존 라이브러리 없이도 정확하게 변환된다.
47+
- `9 * 60 * 60 * 1000` (32,400,000ms)를 더하는 건 단순하지만 서머타임이 없는 한국에서는 완벽하게 동작한다.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
layout: post
3+
title: "Next.js Prisma Singleton Pattern - Fix Hot Reload Connection Leaks"
4+
date: 2025-01-20 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Next.js, Prisma, singleton, database, TypeScript]
7+
author: "Kevin Park"
8+
lang: en
9+
excerpt: "Fix Prisma client connection leaks during Next.js hot reload with a simple singleton pattern."
10+
---
11+
12+
## Problem
13+
14+
In Next.js dev mode, every file save triggers a hot reload that creates a new `PrismaClient()` instance. Database connections pile up until you hit a `Too many connections` error.
15+
16+
## Solution
17+
18+
```typescript
19+
// lib/prisma.ts
20+
import { PrismaClient } from '@prisma/client'
21+
22+
const globalForPrisma = globalThis as unknown as {
23+
prisma: PrismaClient | undefined
24+
}
25+
26+
const prisma = globalForPrisma.prisma ?? new PrismaClient()
27+
28+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
29+
30+
export default prisma
31+
```
32+
33+
## Key Points
34+
35+
- `globalThis` survives hot reloads. Storing the Prisma instance here ensures the existing connection is reused when modules are re-evaluated.
36+
- In production, modules load only once, so storing on `globalThis` is unnecessary. The `NODE_ENV !== 'production'` guard handles this.
37+
- Five lines of code prevent dozens of zombie database connections during development.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
layout: post
3+
title: "Next.jsでPrismaシングルトンパターン - ホットリロードの接続リークを解決"
4+
date: 2025-01-20 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Next.js, Prisma, singleton, database, TypeScript]
7+
author: "Kevin Park"
8+
lang: ja
9+
excerpt: "Next.jsの開発モードでPrismaクライアントの接続が増え続ける問題をシングルトンパターンで解決する方法をご紹介します。"
10+
---
11+
12+
## 問題
13+
14+
Next.jsの開発モードでファイルを修正するたびにホットリロードが発生し、`new PrismaClient()`が毎回新しく実行されます。その結果、DB接続が蓄積され、`Too many connections`エラーが発生します。
15+
16+
## 解決方法
17+
18+
```typescript
19+
// lib/prisma.ts
20+
import { PrismaClient } from '@prisma/client'
21+
22+
const globalForPrisma = globalThis as unknown as {
23+
prisma: PrismaClient | undefined
24+
}
25+
26+
const prisma = globalForPrisma.prisma ?? new PrismaClient()
27+
28+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
29+
30+
export default prisma
31+
```
32+
33+
## ポイント
34+
35+
- `globalThis`はホットリロードしても初期化されません。ここにPrismaインスタンスを保存しておけば、ファイルが再読み込みされても既存の接続を再利用できます。
36+
- 本番環境ではモジュールは一度だけ読み込まれるため、`globalThis`への保存は不要です。そのため`NODE_ENV !== 'production'`の条件を付けています。
37+
- たった5行のコードで、開発中にDB接続が数十個も溜まるのを防ぐことができます。
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
layout: post
3+
title: "Next.js에서 Prisma 싱글톤 패턴 - 핫리로드 연결 누수 해결"
4+
date: 2025-01-20 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Next.js, Prisma, singleton, database, TypeScript]
7+
author: "Kevin Park"
8+
lang: ko
9+
excerpt: "Next.js 개발 모드에서 Prisma 클라이언트 연결이 계속 쌓이는 문제를 싱글톤 패턴으로 해결하는 방법."
10+
---
11+
12+
## 문제
13+
14+
Next.js 개발 모드에서 파일을 수정할 때마다 핫리로드가 발생하면서 `new PrismaClient()`가 매번 새로 실행된다. 결과적으로 DB 연결이 계속 쌓여서 `Too many connections` 에러가 터진다.
15+
16+
## 해결
17+
18+
```typescript
19+
// lib/prisma.ts
20+
import { PrismaClient } from '@prisma/client'
21+
22+
const globalForPrisma = globalThis as unknown as {
23+
prisma: PrismaClient | undefined
24+
}
25+
26+
const prisma = globalForPrisma.prisma ?? new PrismaClient()
27+
28+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
29+
30+
export default prisma
31+
```
32+
33+
## 핵심 포인트
34+
35+
- `globalThis`는 핫리로드를 해도 초기화되지 않는다. 여기에 Prisma 인스턴스를 저장해두면 파일이 다시 로드되어도 기존 연결을 재사용한다.
36+
- 프로덕션에서는 모듈이 한 번만 로드되니까 `globalThis`에 저장할 필요가 없다. 그래서 `NODE_ENV !== 'production'` 조건을 건다.
37+
- 이 패턴 5줄이면 끝나는데, 이걸 안 하면 개발할 때 DB 연결이 수십 개씩 쌓이는 걸 보게 된다.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
layout: post
3+
title: "Docker Multi-Stage Build for Next.js - Optimize Image Size"
4+
date: 2025-03-10 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Docker, Next.js, multi-stage build, optimization]
7+
author: "Kevin Park"
8+
lang: en
9+
excerpt: "Reduce Next.js Docker image from 1GB to under 200MB with a 3-stage Dockerfile."
10+
---
11+
12+
## Problem
13+
14+
A naive Docker build for Next.js includes the entire `node_modules` directory, resulting in images over 1GB. DevDependencies bloat it even further.
15+
16+
## Solution
17+
18+
```dockerfile
19+
# Stage 1: Install dependencies
20+
FROM node:22-alpine AS deps
21+
WORKDIR /app
22+
RUN apk add --no-cache libc6-compat
23+
COPY package.json package-lock.json* ./
24+
RUN npm ci
25+
26+
# Stage 2: Build
27+
FROM node:22-alpine AS builder
28+
WORKDIR /app
29+
COPY --from=deps /app/node_modules ./node_modules
30+
COPY . .
31+
ENV NEXT_TELEMETRY_DISABLED=1
32+
RUN npm run build
33+
34+
# Stage 3: Run (minimal image)
35+
FROM node:22-alpine AS runner
36+
WORKDIR /app
37+
ENV NODE_ENV=production
38+
RUN addgroup --system --gid 1001 nodejs
39+
RUN adduser --system --uid 1001 nextjs
40+
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
41+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
42+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
43+
USER nextjs
44+
EXPOSE 3000
45+
CMD ["node", "server.js"]
46+
```
47+
48+
## Key Points
49+
50+
- Set `output: 'standalone'` in `next.config.js` to generate `.next/standalone`. Without this, stage 3 won't work.
51+
- The 3-stage split (`deps``builder``runner`) ensures only production-necessary files end up in the final image.
52+
- Creating a non-root user with `adduser` is a security baseline. Even if the container is compromised, there's no root access.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
layout: post
3+
title: "Dockerマルチステージビルドで Next.js イメージを最適化する"
4+
date: 2025-03-10 09:00:00 +0900
5+
categories: [Development, Tips]
6+
tags: [Docker, Next.js, multi-stage build, optimization]
7+
author: "Kevin Park"
8+
lang: ja
9+
excerpt: "Dockerマルチステージビルドで Next.js イメージを1GBから200MB以下に削減する実践的なDockerfileをご紹介します。"
10+
---
11+
12+
## 問題
13+
14+
Next.jsをそのままDockerでビルドすると、`node_modules`全体が含まれてイメージが1GBを超えてしまいます。devDependenciesまで含まれるため、当然の結果です。
15+
16+
## 解決方法
17+
18+
```dockerfile
19+
# Stage 1: 依存関係のインストール
20+
FROM node:22-alpine AS deps
21+
WORKDIR /app
22+
RUN apk add --no-cache libc6-compat
23+
COPY package.json package-lock.json* ./
24+
RUN npm ci
25+
26+
# Stage 2: ビルド
27+
FROM node:22-alpine AS builder
28+
WORKDIR /app
29+
COPY --from=deps /app/node_modules ./node_modules
30+
COPY . .
31+
ENV NEXT_TELEMETRY_DISABLED=1
32+
RUN npm run build
33+
34+
# Stage 3: 実行(最小イメージ)
35+
FROM node:22-alpine AS runner
36+
WORKDIR /app
37+
ENV NODE_ENV=production
38+
RUN addgroup --system --gid 1001 nodejs
39+
RUN adduser --system --uid 1001 nextjs
40+
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
41+
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
42+
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
43+
USER nextjs
44+
EXPOSE 3000
45+
CMD ["node", "server.js"]
46+
```
47+
48+
## ポイント
49+
50+
- `next.config.js``output: 'standalone'`を設定しないと、`.next/standalone`が生成されません。Stage 3が機能するための前提条件です。
51+
- `deps``builder``runner`の3段階に分けることで、最終イメージには実行に必要なファイルのみが含まれます。
52+
- `adduser`で非rootユーザーを作成して実行するのはセキュリティの基本です。コンテナが侵害されてもroot権限はありません。

0 commit comments

Comments
 (0)