๐ฏ ๋ชฉํ
- ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ ํ์๊ฐ์ /๋ก๊ทธ์ธ ๊ตฌํ
- ๋ก๊ทธ์ธ ์ํ ์ ์ง(
onAuthStateChanged) - ํค๋์ ๋ก๊ทธ์ธ ์ํ ํ์(๋ก๊ทธ์์ ํฌํจ)
- ๊ฐ๋จํ ๋ณดํธ ํ์ด์ง(
/dashboard) ๊ตฌ์ฑ
1) ํค๋ ์ปดํฌ๋ํธ (๋ก๊ทธ์ธ ์ํ ํ์)
components/HeaderAuth.tsx
- ์ ์ญ ํค๋์์ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ค์๊ฐ ๋ฐ์ํฉ๋๋ค.
- ๋ก๊ทธ์ธ ์ํ๋ฉด โ์ด๋ฉ์ผ + ๋ก๊ทธ์์ ๋ฒํผโ, ์๋๋ผ๋ฉด โ๋ก๊ทธ์ธ/ํ์๊ฐ์ ๋งํฌโ๋ฅผ ๋ณด์ฌ์ค๋๋ค.
'use client';
import { useEffect, useState } from 'react';
import { auth } from '@/lib/firebase.client';
import { onAuthStateChanged, signOut, User } from 'firebase/auth';
import Link from 'next/link';
export default function HeaderAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsub = onAuthStateChanged(auth, (u) => {
setUser(u);
setLoading(false);
});
return () => unsub();
}, []);
if (loading) return null;
return (
<header style={{display:'flex',gap:12,alignItems:'center',padding:'12px 16px',borderBottom:'1px solid #eee'}}>
<Link href="/">Home</Link>
<Link href="/dashboard">Dashboard</Link>
<div style={{marginLeft:'auto'}}>
{user ? (
<div style={{display:'flex',gap:12,alignItems:'center'}}>
<span style={{fontSize:14}}>์๋
ํ์ธ์, {user.email}</span>
<button onClick={() => signOut(auth)} style={{padding:'6px 10px',border:'1px solid #ddd',borderRadius:8}}>
๋ก๊ทธ์์
</button>
</div>
) : (
<div style={{display:'flex',gap:12}}>
<Link href="/login">๋ก๊ทธ์ธ</Link>
<Link href="/signup">ํ์๊ฐ์
</Link>
</div>
)}
</div>
</header>
);
}
๋์ ํ๋ฆ(์์๋๋ก)
- ๋ง์ดํธ ์
onAuthStateChanged(auth, cb)๋ก Firebase์๊ฒ โํ์ฌ ์ฌ์ฉ์โ๋ฅผ ๋ฌผ์ด๋ด ๋๋ค. - Firebase๊ฐ ๋ก์ปฌ ์ธ์
์ ๋ณต์ํ๊ณ ์ฌ์ฉ์(
User | null)๋ฅผ ์ฝ๋ฐฑ์ผ๋ก ์ ๋ฌํฉ๋๋ค. - ์ ๋ฌ๋ ๊ฐ์ผ๋ก
user์ํ๋ฅผ ๊ฐฑ์ ํ๊ณ , ์ด๊ธฐ ๋ก๋ฉ ํ์์ฉloading์false๋ก ๋ฐ๊ฟ๋๋ค. - ์ดํ ๋ก๊ทธ์ธ/๋ก๊ทธ์์/ํ ํฐ๋ณ๊ฒฝ์ด ๋ฐ์ํ๋ฉด ์ฝ๋ฐฑ์ด ์๋ ์ฌํธ์ถ๋์ด UI๊ฐ ์ฆ์ ๋ฐ๋๋๋ค.
- ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ผ์ง ๋
unsub()๋ก ๋ฆฌ์ค๋ ํด์ (๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง).
์ฃผ์ ์ํ๊ฐ
user: ๋ก๊ทธ์ธํ ์ ์ ๊ฐ์ฒด(null์ด๋ฉด ๋น๋ก๊ทธ์ธ)loading: Firebase๊ฐ ์ด๊ธฐ ํ์ ์ ๋๋๋์ง ์ฌ๋ถ(๊น๋ฐ์ ๋ฐฉ์ง)
๋ ๋ ์กฐ๊ฑด
loading์ผ ๋null์ ๋ฐํํด ์ด๊ธฐ ๊น๋ฐ์์ ์ค์ ๋๋ค.user๊ฐ ์์ผ๋ฉด ์ด๋ฉ์ผ/๋ก๊ทธ์์ ๋ฒํผ, ์์ผ๋ฉด ๋ก๊ทธ์ธ/ํ์๊ฐ์ ๋งํฌ๋ฅผ ๋ ธ์ถํฉ๋๋ค.
์ด๋ฒคํธ
๋ก๊ทธ์์:
signOut(auth)ํธ์ถ๋ง์ผ๋ก ์ํ๊ฐ ๋ฐ๋๋๋ค(๋ณ๋ ๋ผ์ฐํ ๋ถํ์).onAuthStateChanged๊ฐuser=null์ ์ ๋ฌ โ UI๊ฐ ์ฆ์ ๊ฐฑ์ ๋ฉ๋๋ค.
app/layout.tsx์ ํค๋ ์ถ๊ฐ:
import './globals.scss';
import HeaderAuth from '@/components/HeaderAuth';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
<body>
<HeaderAuth />
{children}
</body>
</html>
);
}
2) ๋ก๊ทธ์ธ ํ์ด์ง
- ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ๋ก ๋ก๊ทธ์ธ ํผ์ ์ ๊ณตํ๊ณ , ์ฑ๊ณต ์ ๋ณดํธ ํ์ด์ง(
/dashboard)๋ก ์ด๋ํฉ๋๋ค.
app/login/page.tsx
'use client';
import { FormEvent, useState } from 'react';
import { auth } from '@/lib/firebase.client';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
export default function LoginPage() {
const router = useRouter();
const [email, setEmail] = useState('');
const [pw, setPw] = useState('');
const [err, setErr] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
setErr(null);
setLoading(true);
try {
await signInWithEmailAndPassword(auth, email, pw);
router.replace('/dashboard');
} catch (e: any) {
setErr(e?.message ?? '๋ก๊ทธ์ธ ์คํจ');
} finally {
setLoading(false);
}
};
return (
<main style={{ maxWidth: 420, margin: '40px auto', padding: 16 }}>
<h1>๋ก๊ทธ์ธ</h1>
<form
onSubmit={onSubmit}
style={{ display: 'grid', gap: 10, marginTop: 16 }}
>
<input
type='email'
placeholder='์ด๋ฉ์ผ'
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{ padding: 10, border: '1px solid #ddd', borderRadius: 8 }}
/>
<input
type='password'
placeholder='๋น๋ฐ๋ฒํธ'
value={pw}
onChange={(e) => setPw(e.target.value)}
style={{ padding: 10, border: '1px solid #ddd', borderRadius: 8 }}
/>
{err && <p style={{ color: 'crimson', fontSize: 14 }}>{err}</p>}
<button
disabled={loading}
style={{ padding: '10px 12px', borderRadius: 8 }}
>
{loading ? '๋ก๊ทธ์ธ ์คโฆ' : '๋ก๊ทธ์ธ'}
</button>
</form>
<p style={{ marginTop: 12, fontSize: 14 }}>
๊ณ์ ์ด ์์ผ์ ๊ฐ์? <Link href='/signup'>ํ์๊ฐ์
</Link>
</p>
</main>
);
}
๋์ ํ๋ฆ(ํผ ์ ์ถ)
onSubmit์์ ๊ธฐ๋ณธ ์ ์ถ ์ด๋ฒคํธ ๋ง๊ธฐ โ SPA ๋ด ๋น๋๊ธฐ ์ฒ๋ฆฌloading์true๋ก ๋ฐ๊พธ๊ณ , ์๋ฌ ์ํ ์ด๊ธฐํsignInWithEmailAndPassword(auth, email, pw)ํธ์ถ- ์ฑ๊ณต ์
router.replace('/dashboard')- replace๋ฅผ ์ฐ๋ ์ด์ : ๋ค๋ก ๊ฐ๊ธฐ๋ฅผ ๋๋ฌ๋ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋๋์๊ฐ์ง ์๊ฒ ํ๊ธฐ ์ํจ
- ์คํจ ์ ์๋ฌ ๋ฉ์์ง๋ฅผ
err์ ์ ์ฅํด ์ฌ์ฉ์์๊ฒ ํ์ - ๋ง์ง๋ง์
loading=false
์ฃผ์ ์ํ๊ฐ
email,pw: ์ ์ด ์ปดํฌ๋ํธ๋ก ์ ๋ ฅ๊ฐ์ ์ํ๋ก ๊ด๋ฆฌerr: Firebase ์๋ฌ ๋ฉ์์ง(์ค์๋น์ค์์ ์ฝ๋๋ณ ํ๊ธํ ๊ถ์ฅ)loading: ์ ์ถ ์ค ๋ฒํผ ๋นํ์ฑํ/ํ ์คํธ ๋ณ๊ฒฝ
ํผ/UX ํฌ์ธํธ
- ์ธํ์ ๊ธฐ๋ณธ์ ์ธ ์คํ์ผ๊ณผ placeholder๋ง ํฌํจ(ํ์ ์
autoComplete์ถ๊ฐ ๊ถ์ฅ) loading๋์ ๋ฒํผ์ ๋นํ์ฑํํด ์ค๋ณต ์ ์ถ ๋ฐฉ์ง- ์คํจ ์ ์ฌ์ฉ์ ์นํ์ ๋ฌธ๊ตฌ๋ก ๋งคํํ๋ฉด ์ดํ๋ฅ ๊ฐ์(์: โ์ด๋ฉ์ผ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.โ)
๋ด๋น๊ฒ์ด์
- ํ๋จ์ โํ์๊ฐ์ โ ๋งํฌ๋ฅผ ์ ๊ณตํด ์ ํ ๋์ ํ๋ณด
- ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ๋ณดํธ ํ์ด์ง๋ก ์ด๋ํ์ง๋ง, ๋์ค์
next์ฟผ๋ฆฌ ํจํด(/login?next=/target)์ ๋์ ํ๋ฉด ๋ ์์ฐ์ค๋ฌ์ด ๋ณต๊ท UX ๊ตฌํ ๊ฐ๋ฅ
3) ํ์๊ฐ์ ํ์ด์ง
์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ๋ก ๊ณ์ ์ ์์ฑํ๊ณ , ์ฑ๊ณตํ๋ฉด ์๋ ๋ก๊ทธ์ธ ์ํ๋ก /dashboard๋ก ์ด๋ํฉ๋๋ค.
app/signup/page.tsx
'use client';
import { FormEvent, useState } from 'react';
import { auth } from '@/lib/firebase.client';
import { createUserWithEmailAndPassword } from 'firebase/auth';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
export default function SignupPage() {
const router = useRouter();
const [email, setEmail] = useState('');
const [pw, setPw] = useState('');
const [err, setErr] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const onSubmit = async (e: FormEvent) => {
e.preventDefault();
setErr(null);
setLoading(true);
try {
await createUserWithEmailAndPassword(auth, email, pw);
router.replace('/dashboard');
} catch (e: any) {
setErr(e?.message ?? 'ํ์๊ฐ์
์คํจ');
} finally {
setLoading(false);
}
};
return (
<main style={{ maxWidth: 420, margin: '40px auto', padding: 16 }}>
<h1>ํ์๊ฐ์
</h1>
<form
onSubmit={onSubmit}
style={{ display: 'grid', gap: 10, marginTop: 16 }}
>
<input
type='email'
placeholder='์ด๋ฉ์ผ'
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{ padding: 10, border: '1px solid #ddd', borderRadius: 8 }}
/>
<input
type='password'
placeholder='๋น๋ฐ๋ฒํธ(6์ ์ด์)'
value={pw}
onChange={(e) => setPw(e.target.value)}
style={{ padding: 10, border: '1px solid #ddd', borderRadius: 8 }}
/>
{err && <p style={{ color: 'crimson', fontSize: 14 }}>{err}</p>}
<button
disabled={loading}
style={{ padding: '10px 12px', borderRadius: 8 }}
>
{loading ? '์์ฑ ์คโฆ' : 'ํ์๊ฐ์
'}
</button>
</form>
<p style={{ marginTop: 12, fontSize: 14 }}>
์ด๋ฏธ ๊ณ์ ์ด ์์ผ์ ๊ฐ์? <Link href='/login'>๋ก๊ทธ์ธ</Link>
</p>
</main>
);
}
๋์ ํ๋ฆ(ํผ ์ ์ถ)
onSubmit์์ ๊ธฐ๋ณธ ๋์ ๋ง๊ธฐ โloading=true, ์๋ฌ ์ด๊ธฐํcreateUserWithEmailAndPassword(auth, email, pw)ํธ์ถ- Firebase ์ ์ฑ ์ ๋น๋ฐ๋ฒํธ 6์ ์ด์ ํ์(placeholder๋ก ์๋ด)
- ์ฑ๊ณต ์ Firebase๊ฐ ์ฆ์ ๋ก๊ทธ์ธ ์ํ๊ฐ ๋๋ฉฐ
router.replace('/dashboard') - ์คํจ ์ ์๋ฌ ๋ฉ์์ง ํ์(์ด๋ฏธ ์ฌ์ฉ ์ค ์ด๋ฉ์ผ, ์ฝํ ๋น๋ฐ๋ฒํธ ๋ฑ)
- ๋ง์ง๋ง์
loading=false
์ฃผ์ ์ํ๊ฐ/๋ ๋
email,pw์ ์ด ์ธํ +err,loading์ ๋ก๊ทธ์ธ ํ์ด์ง์ ๋์ผ ํจํด- ์๋ฌ ์ฝ๋๋ณ ๋ฉ์์ง ๋งคํ์ ์ถ๊ฐํ๋ฉด ๊ฐ์ ์คํจ์ ์์ธ์ ๋ช ํํ ์๋ด ๊ฐ๋ฅ
UX/๋ณด์ ํฌ์ธํธ
type="password"๋ก ์ ๋ ฅ ๊ฐ๋ฆผ ์ฒ๋ฆฌ- ํ์ ์ ํด๋ผ์ด์ธํธ์์ ๊ธฐ๋ณธ ํ์ ๊ฒ์ฆ(์ด๋ฉ์ผ ์ ๊ท์, ๋น๋ฐ๋ฒํธ ๊ธธ์ด) ํ Firebase ํธ์ถ โ ๋ถํ์ํ API ์์ฒญ ๊ฐ์
- ๊ฐ์ ์งํ ํ๋กํ ์ธํ ํ๋ฆ(๋๋ค์ ์ค์ ๋ฑ)์ผ๋ก ํ์ฅํ๊ธฐ ์ข์
4) ํด๋ผ์ด์ธํธ ๋ณดํธ ๊ฐ๋ ์ปดํฌ๋ํธ
- ๋ก๊ทธ์ธํ์ง ์์ ์ฌ์ฉ์๊ฐ
/dashboard๋ฑ ๋ณดํธ๋ ํ์ด์ง์ ์ ๊ทผํ์ ๋, ์๋์ผ๋ก/login์ผ๋ก ๋๋ ค๋ณด๋ด๋ ํด๋ผ์ด์ธํธ ๋ณดํธ ๊ฐ๋ ์ปดํฌ๋ํธ์ ๋๋ค. - ์ฆ, โ๋ก๊ทธ์ธํ ์ฌ๋๋ง ์ด ์ปดํฌ๋ํธ ์์ชฝ์ children์ ๋ณผ ์ ์๊ฒโ ๋ง๋ค์ด ์ค๋๋ค.
components/AuthGate.tsx
'use client';
import { ReactNode, useEffect, useState } from 'react';
import { auth } from '@/lib/firebase.client';
import { onAuthStateChanged } from 'firebase/auth';
import { useRouter } from 'next/navigation';
export default function AuthGate({ children }: { children: ReactNode }) {
const router = useRouter();
const [ready, setReady] = useState(false);
const [authed, setAuthed] = useState(false);
useEffect(() => {
const unsub = onAuthStateChanged(auth, (u) => {
setAuthed(!!u);
setReady(true);
if (!u) router.replace('/login');
});
return () => unsub();
}, [router]);
if (!ready) return null; // ๋๋ ๋ก๋ฉ ์คํผ๋
return <>{authed ? children : null}</>;
}
๋์ ์๋ฆฌ
๋ง์ดํธ ์
onAuthStateChanged(auth, callback)์ ์คํํฉ๋๋ค.- Firebase๊ฐ ํ์ฌ ๋ก๊ทธ์ธ ์ํ(
User | null)๋ฅผ ์๋ ค์ค๋๋ค. - ์ด ์์ ์์
auth๊ฐ ๋ก์ปฌ ์ธ์ ์ ๋ณต์ํ๋ฏ๋ก ๋ธ๋ผ์ฐ์ ๋ฅผ ์๋ก๊ณ ์นจํด๋ ์ ์ง๋ฉ๋๋ค.
- Firebase๊ฐ ํ์ฌ ๋ก๊ทธ์ธ ์ํ(
์ฝ๋ฐฑ ๋ด๋ถ์์:
์ฌ์ฉ์๊ฐ ์์ผ๋ฉด
setAuthed(true)์์ผ๋ฉด
setAuthed(false)ํrouter.replace('/login')์ผ๋ก ์ด๋์ํต๋๋ค.โ ๋น๋ก๊ทธ์ธ ์ฌ์ฉ์๊ฐ ๋ณดํธ ํ์ด์ง๋ฅผ ์ง์ ์ ๋ ฅํด ๋ค์ด์ค๋ ๊ฒ์ ์ฐจ๋จํฉ๋๋ค.
ready์ํ๋ โFirebase๊ฐ ์ด๊ธฐ ํ๋จ์ ๋ง์ณค๋คโ๋ ์๋ฏธ๋ก,ํ์ ์ด ๋๋๊ธฐ ์ ์๋
null์ ๋ฐํํด ๋ก๋ฉ ์ค ๊น๋ฐ์์ ๋ฐฉ์งํฉ๋๋ค.
์ฃผ์ ์ํ๊ฐ
| ์ํ | ์ค๋ช |
|---|---|
ready |
Firebase์ ์ธ์ฆ ์ฌ๋ถ ํ๋จ์ด ์๋ฃ๋๋์ง ์ฌ๋ถ |
authed |
ํ์ฌ ๋ก๊ทธ์ธ๋์ด ์๋์ง ์ฌ๋ถ (boolean) |
ready๊ฐfalse์ผ ๋๋ ์๋ฌด๊ฒ๋ ๋ ๋ํ์ง ์์โ๋ก๊ทธ์ธ ํ์ ์ ํ์ด์ง๊ฐ ์ ๊น ๋ณด์๋ค ์ฌ๋ผ์ง๋โ ํ์์ ๋ฐฉ์งํฉ๋๋ค.
authed๊ฐtrue์ผ ๋๋ง ์ค์ ํ์ด์ง ์ฝํ ์ธ (children)๋ฅผ ๋ ๋ํฉ๋๋ค.
๋ฆฌ๋ค์ด๋ ํธ ์ฒ๋ฆฌ
router.replace('/login')์ ์ฌ์ฉํ ์ด์ ๋๋ค๋ก ๊ฐ๊ธฐ๋ฅผ ๋๋ฌ๋ ๋ค์ ๋ณดํธ ํ์ด์ง๋ก ๋์์ค์ง ์๊ฒ ํ๊ธฐ ์ํจ์ ๋๋ค.
(์ฆ, ๋ธ๋ผ์ฐ์ ํ์คํ ๋ฆฌ๋ฅผ ์๋ก ๋ฎ์ด์์๋๋ค.)
router.push()๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค๋ก๊ฐ๊ธฐ ์/dashboard๋ก ๋ค์ ๋์๊ฐ ์ ์์ด์ธ์ฆ ํ๋ฆ์์๋ ๋ณดํต
replace()๋ฅผ ๊ถ์ฅํฉ๋๋ค.
ํ๊ณ (๋ณด์ ๊ด์ )
์ด ๋ฐฉ์์ ํด๋ผ์ด์ธํธ ๋จ ๋ณดํธ(Client-Side Guard) ๋ก์ง์ ๋๋ค.
์ฆ, ํ์ด์ง๊ฐ ๋ก๋๋ ๋ค JavaScript์์ ์ ๊ทผ์ ๋ง๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์
์์ ํ ๋ณด์์ ์๋๋๋ค. (์ด๊ธฐ HTML์ ์ด๋ฏธ ๋ค์ด๋ก๋๋ ์ํ)
5๊ฐ์์ ๋ค๋ฃฐ Server Actions + Admin SDK ๋จ๊ณ๋ฅผ ์ ์ฉํ๋ฉด
์๋ฒ์์ ํ ํฐ์ ๊ฒ์ฆํด, ๋น๋ก๊ทธ์ธ ์ฌ์ฉ์๋ ์์ ํ์ด์ง HTML์ ๋ฐ์ง ๋ชปํ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
โ ๊ทธ๋๊ฐ ์ง์ง โ์๋ฒ ๋ณดํธ(Server-Side Protection)โ ๋จ๊ณ์ ๋๋ค.
4) ๋์๋ณด๋ ํ์ด์ง(๋ก๊ทธ์ธ ํ์)
AuthGate๋ก ๋ณดํธ๋ ์ค์ โ๋ก๊ทธ์ธ ์ ์ฉ ํ์ด์งโ์ ๋๋ค.- ๋ก๊ทธ์ธํ ์ฌ์ฉ์๋ง ์ด ํ์ด์ง์ ๋ด์ฉ์ ๋ณผ ์ ์์ต๋๋ค.
app/dashboard/page.tsx
'use client';
import AuthGate from '@/components/AuthGate';
export default function DashboardPage() {
return (
<AuthGate>
<main style={{ maxWidth: 720, margin: '40px auto', padding: 16 }}>
<h1>๋์๋ณด๋</h1>
<p>๋ก๊ทธ์ธํ ์ฌ์ฉ์๋ง ๋ณผ ์ ์๋ ํ์ด์ง์
๋๋ค.</p>
</main>
</AuthGate>
);
}
๊ตฌ์กฐ ์ค๋ช
<AuthGate>
<main> ... </main>
</AuthGate>
<AuthGate>๊ฐ ๊ฐ์ธ๊ณ ์๊ธฐ ๋๋ฌธ์,๋น๋ก๊ทธ์ธ ์ฌ์ฉ์๋ ๋ด๋ถ ์ฝํ ์ธ (
main)์ ์ ํ ๋ณผ ์ ์์ต๋๋ค.AuthGate๊ฐ/login์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ์ํค๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋์ ์์
- ์ฌ์ฉ์๊ฐ
/dashboardURL์ ์ ๊ทผํฉ๋๋ค. - ํ์ด์ง๊ฐ ๋ ๋๋ง๋๋ฉด์
AuthGate๊ฐ ๋จผ์ ์คํ๋ฉ๋๋ค. - Firebase Auth๊ฐ ํ์ฌ ๋ก๊ทธ์ธ ์ํ๋ฅผ ํ์ธํฉ๋๋ค.
- ๋์ ์ค๋ช
- โ
๋ก๊ทธ์ธ๋์ด ์์ผ๋ฉด โ
<main>๋ด์ฉ์ ๋ ๋ํฉ๋๋ค. - โ ๋ก๊ทธ์ธ ์ ๋์ด ์์ผ๋ฉด โ
/login์ผ๋ก ์ด๋์ํต๋๋ค.
- โ
๋ก๊ทธ์ธ๋์ด ์์ผ๋ฉด โ
ํ ์คํธ ์๋๋ฆฌ์ค
| ์๋๋ฆฌ์ค | ๊ฒฐ๊ณผ |
|---|---|
๋ก๊ทธ์ธ ์ ํ ์ํ๋ก /dashboard ์ ๊ทผ |
/login์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ |
| ํ์๊ฐ์ ์งํ ์๋ ๋ก๊ทธ์ธ ์ํ | /dashboard ์ ๊ทผ ์ฑ๊ณต |
| ์๋ก๊ณ ์นจํด๋ ์ธ์ ์ ์ง | Firebase๊ฐ ์๋์ผ๋ก ๋ณต์ |
๋ก๊ทธ์์ ํ /dashboard ์ ๊ทผ |
๋ค์ /login์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ |
5) ๊ฐ๋จ SCSS
app/globals.scss
:root {
--radius: 10px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, Segoe UI, Roboto, 'Apple SD Gothic Neo',
sans-serif;
}
li {
list-style-type: none;
}
a {
text-decoration: none;
color: #1a73e8;
}
input,
button {
font: inherit;
}
button {
cursor: pointer;
}
6) ๋์ ํ์ธ
/signup์์ ํ์๊ฐ์ โ ์๋ ๋ก๊ทธ์ธ โ/dashboard์ง์- ํค๋์ ์ด๋ฉ์ผ ํ์, ๋ก๊ทธ์์ ๋ฒํผ ํด๋ฆญ ์ ์ํ ๋ณ๊ฒฝ ํ์ธ
/login์์ ๋ก๊ทธ์ธ ํ/dashboard์ ๊ทผ ๊ฐ๋ฅ
๐ฌ ๋๊ธ
โป ๋ก๊ทธ์ธ ํ ๋๊ธ์ ์์ฑํ ์ ์์ต๋๋ค.