๐ฏ ๋ชฉํ
- Next.js(App Router) ํ๋ก์ ํธ ์์ฑ
- Firebase Console์์ ํ๋ก์ ํธ ์์ฑ & ์๋น์ค ์ผ๊ธฐ(Auth/Firestore/Storage)
.env.localํ๊ฒฝ๋ณ์ ์ค์ firebase.client.ts์ด๊ธฐํ ํ์ผ ๊ตฌ์ฑ- Firestore์ ํ ์คํธ ๋ฐ์ดํฐ ์ฐ๊ธฐ/์ฝ๊ธฐ๊น์ง ํ์ธ
1) Next.js ํ๋ก์ ํธ ์์ฑ
npx create-next-app@latest nextjs-firebase
# โ TypeScript: Yes
# โ ESLint: Yes
# โ Tailwind: No (SCSS ์ฌ์ฉํ ์์ )
# โ App Router: Yes
# โ src/: ์ ํ์ ์์ (์ฌ๊ธฐ์ ๊ธฐ๋ณธ ๋ฃจํธ ์ฌ์ฉ)
# โ import alias: Yes (@/*)
cd nextjs-firebase
npm i
npm i -D sass
โป ์ถํ ๊ฒฝ๋ก๋ฅผ ์ค๋ช ํ ๋ ์ต์๋จ src๋ ์ ์ธํ๋๋ก ํ๊ฒ ์ต๋๋ค.
styles/globals.scss๋ฅผ app/layout.tsx์ ์ถ๊ฐ:
// app/layout.tsx
import './globals.scss';
2) Firebase ์ฝ์ ์ค์
- [console.firebase.google.com]์์ ์ ํ๋ก์ ํธ ์์ฑ (์:
nextjs-firebase-course) - Authentication โ Sign-in method โ Email/Password ํ์ฑํ(+ Google Sign-in์ 3๊ฐ์์)
- Firestore โ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง๋ค๊ธฐ โ ๋ชจ๋: ํ ์คํธ(๊ฐ๋ฐ ๋จ๊ณ) โ ์์น: asia-northeast3(์์ธ)
- Storage โ ์์ํ๊ธฐ
- ์คํ ๋ฆฌ์ง๋ถํฐ๋ ์๊ธ ๊ฒฐ์ ๊ฐ ํ์ํ๋ฏ๋ก GCP์ Cloud Billing ๊ณ์ ์ด ์ฐ๋๋์ด์ผ ํฉ๋๋ค.
- ํ๋ก์ ํธ ์ค์ โ ์น ์ฑ ์ถ๊ฐ โ
Firebase SDK snippet์์apiKey๋ฑ ์ค์ ๊ฐ ํ์ธ
A. ์ ํ๋ก์ ํธ ์์ฑ

.png?alt=media&token=57dd014c-6bd8-41a7-a854-0c826bb08a0d)
.png?alt=media&token=0787deac-d29c-46c4-abe3-db6b400b800c)
.png?alt=media&token=66897694-3ea3-4377-b2e9-0ed4fcbd5086)
B. Authentication ์ค์
.png?alt=media&token=6a3797d8-bc66-4c16-be94-261f8fdbe551)
.png?alt=media&token=e9cbee1f-fa97-4d88-b689-6856f7fe932e)
.png?alt=media&token=f92c5120-2d9f-4aca-91d0-031a72974a83)
.png?alt=media&token=1b9bca25-09bf-406a-8787-27aadcb7c692)
.png?alt=media&token=772560e0-53b3-42a7-afba-e0c8e414a803)
C. Firestore ์ค์
.png?alt=media&token=81e682b0-0d0c-4001-b813-408e99907d44)
.png?alt=media&token=79b6cdab-9c8a-4d44-8d7a-f68bc1556bd3)
.png?alt=media&token=4527d17c-c0dc-4959-8582-d1ef225f6db4)
Firestore Standard vs Enterprise: Technical Comparison (by ChatGPT 5)
๐ 1. Firestore Standard vs Enterprise ๊ตฌ์กฐ์ ์ฐจ์ด
| ํญ๋ชฉ | Standard | Enterprise |
|---|---|---|
| ์คํ ๋ฆฌ์ง ๊ตฌ์กฐ | ํ์ด๋ธ๋ฆฌ๋ (SSD + HDD) | ์ ๋ฉด SSD |
| ์ฟผ๋ฆฌ ์์ง | ํ์ค | ๊ณ ๊ธ (์ํฐํ๋ผ์ด์ฆ์ฉ, MongoDB ํธํ ํฌํจ) |
| ๋ฌธ์ ํฌ๊ธฐ ํ๋ | 1MiB | 4MiB |
| ๋ฐฑ์ ๋ฐ ๋ณต๊ตฌ | ๋์ผ | ๋์ผ |
| ์ง์ ํ๊ฒฝ | ๋์ผ | ๋์ผ |
์ฌ๊ธฐ์ ๊ฐ์ฅ ์ง์ ์ ์ธ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋ง๋๋ ๋ถ๋ถ์ด ๋ฐ๋ก "์คํ ๋ฆฌ์ง์ ์ฟผ๋ฆฌ ์์ง"์ ๋๋ค.
โก 2. ์๋ ์ฐจ์ด์ ์์ธ
์คํ ๋ฆฌ์ง ํ์
- ํ์ค: ์ผ๋ถ ๋ฐ์ดํฐ๋ HDD(ํ๋๋์คํฌ) ๊ธฐ๋ฐ์ผ๋ก ์ ์ฅ๋์ด ์์ด, ์ฝ๊ธฐ/์ฐ๊ธฐ ์๋๊ฐ ์๋์ ์ผ๋ก ๋๋ฆฝ๋๋ค.
- ์ํฐํ๋ผ์ด์ฆ: ์ ๋ถ SSD ๊ธฐ๋ฐ์ผ๋ก ์ด์๋์ด, ๋๋์ ๋๋ค ์ก์ธ์ค ์ฟผ๋ฆฌ๋ ์ธ๋ฑ์ค ์กฐํ ์ ์๋๊ฐ ๋น ๋ฆ ๋๋ค.
- ํนํ ๋์ฉ๋ ๋ฌธ์ ์งํฉ์ ๋์์ผ๋ก ํ๋ ๋ณตํฉ ์ฟผ๋ฆฌ์์ SSD์ ์ด์ ์ด ๋๋๋ฌ์ง๋๋ค.
์ฟผ๋ฆฌ ์์ง ์ฐจ์ด
Enterprise๋ MongoDB ํธํํ ์ฟผ๋ฆฌ ์์ง์ ์ฌ์ฉํ๋ฉฐ, ๋ณต์กํ ํํฐ๋ง๊ณผ ์กฐ์ธ ์ฑ๊ฒฉ์ ์ฟผ๋ฆฌ์ ์ต์ ํ๋์ด ์์ต๋๋ค.
Standard๋ณด๋ค ์ธ๋ฑ์ค ๊ฒ์ ๋ฐ ์ฟผ๋ฆฌ ์ต์ ํ ๊ณผ์ ์ด ๊ฐ์ ๋์ด ์์ด,
๊ฐ์ ์ฟผ๋ฆฌ๋ผ๋ ์ํฐํ๋ผ์ด์ฆ๊ฐ 10~30% ๋น ๋ฅด๊ฒ ์๋ตํ๋ ์ฌ๋ก๊ฐ ์์ต๋๋ค.
์บ์ฑ ๋ฐ ๋ณต๊ตฌ ๋ฉ์ปค๋์ฆ
- Enterprise๋ SSD ๊ธฐ๋ฐ ์บ์ฑ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ์ฌ ์ต๊ทผ ์ ๊ทผํ ๋ฐ์ดํฐ๋ฅผ ๋ ๋น ๋ฅด๊ฒ ๋ฐํํฉ๋๋ค.
- ๋๋์ ์ค์๊ฐ ์ ๋ฐ์ดํธ(์: ์ค์๊ฐ ๋์๋ณด๋, ๋ก๊ทธ ์์คํ )์ผ์๋ก ์ฐจ์ด๊ฐ ๋ฉ๋๋ค.
๐งช 3. ์ค์ ์ฒด๊ฐ ์์
| ์๋๋ฆฌ์ค | Standard | Enterprise |
|---|---|---|
๊ฐ๋จํ where() ์ฟผ๋ฆฌ |
๊ฑฐ์ ๋์ผ | ๋์ผ |
| ๋ณตํฉ ์ฟผ๋ฆฌ (2~3๊ฐ ํํฐ + ์ ๋ ฌ) | ์ฝ๊ฐ ๋๋ฆผ | ์ฝ 1.2~1.5๋ฐฐ ๋น ๋ฆ |
| ๋๋ ๋ฌธ์ ์ฝ๊ธฐ (10๋ง ๊ฑด ์ด์) | ์๋ต ์ง์ฐ ๊ฐ๋ฅ | ์ผ์ ํ๊ณ ๋น ๋ฆ |
| ์ค์๊ฐ ์ ๋ฐ์ดํธ ๋น๋ ๋์ ๋ | ๊ฐํ์ ์ง์ฐ ๋ฐ์ | ์์ ์ |
๐งญ 4. ์ ๋ฆฌ
| ํญ๋ชฉ | ์๋ ์ํฅ ์ฌ๋ถ | ์ค๋ช |
|---|---|---|
| ์คํ ๋ฆฌ์ง (SSD vs HDD) | โ ๋์ | I/O ์ฑ๋ฅ ์ง์ ์ฐจ์ด |
| ์ฟผ๋ฆฌ ์์ง (๊ณ ๊ธ/ํ์ค) | โ ์ค๊ฐ | ์ธ๋ฑ์ค ์ต์ ํ ๊ฐ์ |
| ๋ฌธ์ ํฌ๊ธฐ ํ๋ | โ๏ธ ๊ฐ์ ์ | ํฐ ๋ฌธ์๋ฅผ ํ ๋ฒ์ ์ฒ๋ฆฌ ๊ฐ๋ฅ |
| ๋ฐ์ดํฐ ๋ณดํธ ๊ธฐ๋ฅ | โ ์์ | ์๋์ ๋ฌด๊ด |
โ ๊ฒฐ๋ก
์๋ ์ฐจ์ด๋ ๋ถ๋ช ํ ์กด์ฌํฉ๋๋ค.
ํนํ SSD ๊ธฐ๋ฐ ์คํ ๋ฆฌ์ง์ ๊ณ ๊ธ ์ฟผ๋ฆฌ ์์ง ๋๋ถ์ ์ํฐํ๋ผ์ด์ฆ๋ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์ฒ๋ฆฌ ์ ๋ ๋น ๋ฅด๊ณ ์์ ์ ์ ๋๋ค.
๋จ, ์ผ๋ฐ์ ์ธ ์น/์ฑ ์์ค(์์ฒ~์๋ง ๋ฌธ์)์ CRUD๋ผ๋ฉด ์ฒด๊ฐ ์ฐจ์ด๋ ๊ฑฐ์ ์์ต๋๋ค.
ํ์ง๋ง ๋์๋ณด๋, ๋ถ์, ๋ก๊ทธ, ๋ฆฌํฌํ ๋ฑ ๋๋ ์ฟผ๋ฆฌ ์ค์ฌ์ ์์คํ ์ด๋ผ๋ฉด Enterprise ์ ๊ทธ๋ ์ด๋๊ฐ ์๋ ํฅ์์ ํ์คํ ๋์์ด ๋ฉ๋๋ค.
.png?alt=media&token=b0e2d2ee-7afb-4815-a938-ec425406a32c)
.png?alt=media&token=c7b26717-63a9-4180-a8bf-4b187a8a8313)
.png?alt=media&token=19790aa6-7d39-42dd-af27-7be414ce5ecb)
D. Storage ์ค์
.png?alt=media&token=9a41c712-c3bb-43fd-9681-b2894b35f24b)
Cloud Billing ๊ฒฐ์ ๊ณ์ ์ฐ๋์ ์ํด์ ํ๋ก์ ํธ ์ ๊ทธ๋ ์ด๋๋ฅผ ํฉ๋๋ค. Cloud Billing ๊ฒฐ์ ๊ณ์ ์์ฑ ๊ณผ์ ์ ์๋ตํ๊ฒ ์ต๋๋ค. ํด์ธ ๊ฒฐ์ ๊ฐ ๊ฐ๋ฅํ ์นด๋ ์ฐ๋์ด ํ์ํ๋ฉฐ ์ค์ต์ ์ํ ์คํ ๋ฆฌ์ง ๋น์ฉ์ ์ฐ๋ํด๋ ๋๋์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ์ฌ๋ฆฌ์ง ์๋ ์ด์ ๋น์ฉ ๊ฒฐ์ ๊ฐ ๊ฑฐ์ ๋์ง ์์ผ๋ ์ฐธ๊ณ ํด์ฃผ์ธ์. (๋ณด์ ์ค์ ๋ ์ค์ํฉ๋๋ค. ์ด ๋ถ๋ถ์ ์คํ ๋ฆฌ์ง ๋ถ๋ถ ๊ฐ์๋ฅผ ์งํํ๋ฉด์ ๋ ์ค๋ช ํ๊ฒ ์ต๋๋ค.)
์์ฑํ ๊ฒฐ์ ๊ณ์ ์ ์ฐ๊ฒฐํด์ ์๊ธ์ ๋ฅผ Spark ์์ Blaze๋ก ๋ณ๊ฒฝํฉ๋๋ค.
.png?alt=media&token=5c3ce80c-d4cb-49b2-868e-5125e0a02fdc)
.png?alt=media&token=8bd6e121-53a4-44c5-a907-d3f97e161753)
์์ก์ด๋ผ๋ ์๊ธ์ด ๋ฐ์ํ๋ฉด ์ด๋ฉ์ผ๋ก ๋ฐ์ ์ ์๋๋ก ์์ฐ ๊ธ์ก์ 25์์ผ๋ก ์ค์ ํฉ๋๋ค.
.png?alt=media&token=41a78d1f-9226-4978-8b75-73d1b559758d)
.png?alt=media&token=c2ef0c37-43f8-4dce-88c4-ee055fd63eae)
.png?alt=media&token=779bd1e2-01b4-4951-aeb3-b2c03397ff34)
์ข์ธก ํ๋จ์ ๋ณ๊ฒฝ๋ ์๊ธ์ ๋ฅผ ํ์ธํ๊ณ ์คํ ๋ฆฌ์ง๋ฅผ ์์ํฉ๋๋ค.
์ ๋ฃ ์์น์ ASIA-NORTHEAST3 (์์ธ)๋ฅผ ์ฌ์ฉํ๋ฉด ์๋๊ฐ ๋น ๋ฅด์ง๋ง ์ผ๋จ ๋ฌด๋ฃ ์์น(US-CENTRAL1)๋ฅผ ์ ํํฉ๋๋ค.
.png?alt=media&token=247cf269-5398-4d32-b625-8806012ca1fc)
.png?alt=media&token=f97a4f4a-a171-4a0f-bb69-72545eed76ad)
.png?alt=media&token=279a82a5-92f3-4a3c-a1de-62868317944b)
E. ์น์ฑ ์ถ๊ฐ ๋ฐ APIํค ํ์ธ
.png?alt=media&token=6906bda8-9427-45ed-b794-7dc911df4e5d)
.png?alt=media&token=16fcb7bd-0bb7-4c22-81bd-5b72ee08f9d6)
.png?alt=media&token=450e0a61-b1e7-4f23-b560-e59761b09713)
์น ์ฑ์ ๋ฑ๋กํ๊ณ ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ด apiํค๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
.png?alt=media&token=84016855-ca51-46e3-9938-72c58a0a768b)
3) ํ๊ฒฝ๋ณ์ ์ค์
ํ์ด์ด๋ฒ ์ด์ค ์ค์น
npm i firebase
๋ฃจํธ์ .env.local ์์ฑ:
# Client์์ ํ์ํ ๊ฐ์ ๋ฐ๋์ NEXT_PUBLIC_ ์ ๋์ฌ
NEXT_PUBLIC_FIREBASE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=1234567890
NEXT_PUBLIC_FIREBASE_APP_ID=1:1234567890:web:abcdefg1234567
4) Firebase ์ด๊ธฐํ ํ์ผ
lib/firebase.client.ts ์์ฑ:
import { initializeApp, getApps, getApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getStorage } from 'firebase/storage';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY!,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN!,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET!,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID!,
// messagingSenderId, measurementId ํ์ ์ ์ถ๊ฐ
};
const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
export const storage = getStorage(app);
์๋ฒ์์ Admin SDK๋ฅผ ์ธ ๊ณํ์ด๋ฉด ์ถํ lib/firebase.admin.ts๋ฅผ ์ถ๊ฐํฉ๋๋ค(5๊ฐ ์์ ).
์ง๊ธ์ ํด๋ผ์ด์ธํธ SDK๋ง ์ฌ์ฉํฉ๋๋ค.
5) Firestore Rules (๊ฐ๋ฐ์ฉ)
๊ฐ๋ฐ ๋จ๊ณ์์๋ ํ ์คํธ ๊ท์น์ผ๋ก ์์ํ๊ณ , 7๊ฐ์์ ํ๋ก๋์ ๊ท์น์ผ๋ก ๊ฐํํฉ๋๋ค.
6) ํ ์คํธ UI: ๊ธ ์ถ๊ฐ & ๋ชฉ๋ก ๋ณด๊ธฐ
app/page.tsx์ ๊ฐ๋จํ ํผ & ๋ฆฌ์คํธ(ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ) ์ถ๊ฐ:
'use client';
import { FormEvent, useEffect, useState } from 'react';
import { db } from '@/lib/firebase.client';
import {
collection,
addDoc,
serverTimestamp,
getDocs,
query,
orderBy,
} from 'firebase/firestore';
type Post = {
id: string;
title: string;
createdAt?: { seconds: number; nanoseconds: number } | null;
};
export default function HomePage() {
const [title, setTitle] = useState('');
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(false);
const fetchPosts = async () => {
const q = query(collection(db, 'posts'), orderBy('createdAt', 'desc'));
const snap = await getDocs(q);
const list: Post[] = snap.docs.map((d) => ({
id: d.id,
...(d.data() as any),
}));
setPosts(list);
};
useEffect(() => {
fetchPosts();
}, []);
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
if (!title.trim()) return;
setLoading(true);
try {
await addDoc(collection(db, 'posts'), {
title: title.trim(),
createdAt: serverTimestamp(),
});
setTitle('');
await fetchPosts();
} finally {
setLoading(false);
}
};
return (
<main style={{ maxWidth: 720, margin: '40px auto', padding: 16 }}>
<h1>Next.js + Firebase โ Test</h1>
<form
onSubmit={onSubmit}
style={{ display: 'flex', gap: 8, marginTop: 16 }}
>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder='์ ๋ชฉ ์
๋ ฅ'
style={{
flex: 1,
padding: 10,
border: '1px solid #ddd',
borderRadius: 8,
}}
/>
<button
type='submit'
disabled={loading}
style={{ padding: '10px 16px', borderRadius: 8 }}
>
{loading ? '์ ์ฅ ์คโฆ' : '์ถ๊ฐ'}
</button>
</form>
<ul style={{ marginTop: 24, display: 'grid', gap: 12 }}>
{posts.map((p) => (
<li
key={p.id}
style={{ border: '1px solid #eee', padding: 12, borderRadius: 8 }}
>
<strong>{p.title}</strong>
</li>
))}
</ul>
</main>
);
}
7) ์คํ & ํ์ธ
npm run dev
# http://localhost:3000 ์ ์ โ ์ ๋ชฉ ์
๋ ฅ ํ '์ถ๊ฐ' โ Firestore์ ๋ฌธ์ ์์ฑ๋๋์ง ํ์ธ
.png?alt=media&token=0fcef271-c222-43e6-90c6-def5ba827969)
๐ฌ ๋๊ธ
โป ๋ก๊ทธ์ธ ํ ๋๊ธ์ ์์ฑํ ์ ์์ต๋๋ค.