Front/Next.js

firebase를 이용한 Next.js 블로그 앱 만들기

oodada 2024. 5. 5. 01:45

Next.js 블로그 앱 만들기

Next.js를 사용하여 블로그 애플리케이션을 만들어보겠습니다. 블로그 애플리케이션은 다음과 같은 기능을 구현할 수 있습니다.

Next.js 기초 및 설정 포스트를 참고하여 Next.js 프로젝트를 생성합니다.

next.js 블로그 앱 만들기 github 바로가기

1. 프로젝트 개요

1.1. 프로젝트 목표

  • Next를 사용하여 블로그 앱을 만들어보자.
  • Firebase로 사용자 인증을 구현하고 로그인, 회원가입, 로그아웃 기능을 구현한다.
  • Firebase의 기본 개념을 익히고 데이터 조회, 추가, 수정, 삭제를 구현한다.

1.2. 프로젝트 환경

  • Next.js(create-next-app) 프로젝트 생성
  • Firebase (Firestore, Authentication)를 이용한 데이터 관리 및 사용자 인증
  • Firebase Firestore를 이용한 게시판 CRUD(Create, Read, Update, Delete) 구현

1.3. 프로젝트 구조

블로그 애플리케이션은 다음과 같은 구조로 구성됩니다. components 디렉토리에 레이아웃 컴포넌트와 포스트 관련 컴포넌트를 생성하고, pages 디렉토리에 메인 페이지와 포스트 페이지를 생성할 수 있습니다.
public 디렉토리에 이미지와 스타일 파일을 저장할 수 있습니다.

next-blog/
├── public/
│   ├── images/
│   │   └── logo.png
│   ├── styles/
│   │   └── main.css
├── src/
│   ├── components/
│   │   ├── Layout/
│   │   │   └── Layout.tsx
│   │   │   └── Header.tsx
│   │   │   └── Footer.tsx
│   │   ├── Post/
│   │   │   └── PostList.tsx    # 포스트 목록 컴포넌트
│   │   │   └── PostItem.tsx    # 포스트 아이템 컴포넌트
│   │   │   └── PostDetail.tsx  # 포스트 상세 컴포넌트
│   │   │   └── PostEdit.tsx    # 포스트 수정 컴포넌트
│   │   │   └── PostNew.tsx     # 포스트 작성 컴포넌트
│   ├── pages/
│   │   ├── index.tsx
│   │   ├── post
│   │   │   ├── index.tsx    # 포스트 목록 페이지
│   │   │   ├── [id].tsx     # 포스트 상세 페이지
│   │   │   ├── edit.tsx     # 포스트 수정 페이지
│   │   │   └── new.tsx      # 포스트 작성 페이지
│   │   ├── login.tsx    # 로그인 페이지
│   │   ├── signup.tsx   # 회원가입 페이지
│   ├── types/               # 타입 정의
│   │   └── types.ts
│   ├── data/                # 데이터 관리
│   │   └── data.ts
│   ├── firebase/            # Firebase 설정
│   │   ├── firebase.ts     # Firebase 초기화 파일
│   │   ├── auth.ts        # Firebase Authentication 설정
│   │   └── firestore.ts    # Firestore CRUD 기능 구현 파일
│   ├── styles/
│   │   ├── globals.css
│   │   ├── Home.module.css
│   │   └── Post.module.css
├── .gitignore
├── package.json
├── README.md
└── tsconfig.json

3.2. Firestore 프로젝트 구조

src/
│
├── components/   # UI 컴포넌트 폴더
│
├── firebase/     # Firebase 설정과 관련 기능을 포함하는 폴더
│   ├── config.ts # Firebase 초기화 파일
│   └── firestore.ts  # Firestore CRUD 기능 구현 파일
│
├── pages/        # Next.js 페이지
│
└── types/        # TypeScript 타입 정의 폴더

2. 프로젝트 준비

2.2. Firebase 프로젝트 생성

  • [프로젝트 만들기] 버튼을 클릭해 새 프로젝트를 만들어줍니다.

  • 프로젝트 이름을 입력하고, [계속] 버튼을 클릭합니다.

  • 현 프로젝트에서는 Google Analytics를 사용하지 않으므로 선택 해제하고, [프로젝트 만들기] 버튼을 클릭합니다.

  • 프로젝트가 생성되면, [계속] 버튼을 클릭합니다.

  • Firebase 프로젝트가 생성되면, 앱 추가 화면이 나타납니다. 웹 앱을 추가하려면, </> 아이콘을 클릭합니다.

  • 앱의 이름을 입력하고, Firebase Hosting을 설정하지 않으므로 Firebase Hosting 설정 해제합니다. [앱 등록] 버튼을 클릭합니다.

2.3. Firebase SDK 설정

- 패키지 설치

  • Firebase SDK 설정을 위한 구성 요소를 추가하기 위해 npm 패키지를 설치합니다.
npm install firebase

- Firebase 초기화 관리

  • Firebase SDK 설정을 위한 구성 요소를 추가합니다. Firebase SDK 구성을 위한 설정 파일을 생성합니다.
mkdir -p src/firebase
touch src/firebase/firebase.ts
  • Firebase SDK 설정을 위한 구성 요소를 추가합니다. Firebase SDK 구성을 위한 설정 파일을 생성합니다.
// src/firebase/firebase.ts
// Import the functions you need from the SDKs you need
import { initializeApp } from 'firebase/app'
import 'firebase/auth' // Firebase Authentication 기능을 사용하기 위한 import
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
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,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

// Initialize Firebase
const app = initializeApp(firebaseConfig)

export default app

- Firebase 사용하기

  • pages/_app.tsx 파일에서 Firebase SDK 구성을 위한 설정 파일을 import 합니다.
// pages/_app.tsx
import { AppProps } from 'next/app'
import Layout from '@/components/layout/Layout'
import app from '@/firebase/firebase' // Firebase SDK 구성을 위한 설정 파일 import

function MyApp({ Component, pageProps }: AppProps) {
    return (
        <Layout>
            <Component {...pageProps} />
        </Layout>
    )
}

export default MyApp

- 환경 변수 설정

  • Firebase 설정에 필요한 환경 변수들은 .env.local 파일에 저장해야 합니다. Next.js에서는 NEXTPUBLIC 접두어를 사용하여 클라이언트 사이드에서도 환경 변수를 접근할 수 있게 설정합니다.
# .env.local
NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_auth_domain
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_storage_bucket
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_messaging_sender_id
NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id
  • .env.local 파일은 .gitignore 파일에 추가하여 깃허브에 올리지 않도록 설정합니다.
# .gitignore
.env.local
  • 코드에 환경 변수를 사용할 때는 process.env.NEXTPUBLIC 접두어를 사용하여 환경 변수를 사용합니다.
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,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

2.4. Firebase Authentication 설정

- Firebase Authentication 활성화

  • Firebase Console에서 Authentication 메뉴로 이동합니다.

  • [시작하기] 버튼을 클릭하여 Firebase Authentication을 활성화합니다.

  • Firebase Authentication이 활성화되면, [로그인 방법] 탭으로 이동합니다.

  • [google] 로그인 방법을 활성화합니다.

  • Firebase Authentication 설정이 완료되었습니다.

- Firebase Authentication 패키지 설치

- Firebase 인증 설정

이제 인증 기능을 초기화하는 코드를 작성합니다.

src/firebase/auth.ts 파일은 Firebase와 Next.js 프로젝트의 인증 기능을 통합하기 위해 사용자 정의 설정과 함수를 포함하는 파일입니다. 이 파일을 작성하는 목적은 Firebase Authentication 서비스와의 상호 작용을 관리하고, 이를 프로젝트 전반에 걸쳐 쉽게 사용할 수 있도록 하는 것입니다. 이 파일에서 구현하는 주요 기능은 다음과 같습니다:

  1. Firebase 앱 초기화: 프로젝트에 필요한 Firebase 서비스를 사용하기 위해 Firebase 앱을 초기화합니다.
  2. 인증 관련 함수 제공: 로그인, 회원가입, 로그아웃과 같은 인증 관련 함수를 제공합니다.
  3. 인증 상태 관리: 사용자의 로그인 상태를 추적하고, 이를 앱의 다른 부분에서 쉽게 사용할 수 있게 합니다.

참조 및 작성 방법 :
Firebase 공식 문서와 Next.js의 인증 패턴을 참조하여 auth.ts 파일을 작성합니다. 아래는 Firebase 공식 문서와 Next.js의 인증 패턴을 참조할 수 있는 링크입니다.

  • Firebase 공식 문서: Firebase의 다양한 기능을 설정하고 사용하는 방법에 대한 자세한 지침이 포함되어 있습니다. 특히, Firebase 웹 문서에서 제공하는 인증 관련 메서드들의 사용 방법을 참조할 수 있습니다.

  • Next.js 예제 및 튜토리얼: Next.js 커뮤니티와 공식 문서에서 제공하는 인증 튜토리얼을 통해 Next.js에서 인증 시스템을 구현하는 베스트 프랙티스를 배울 수 있습니다.

// src/firebase/firebase.ts
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'

// Firebase 앱 설정
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,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

// Firebase 앱 초기화
const app = initializeApp(firebaseConfig)
export const auth = getAuth(app)
// src/firebase/auth.tsx
// auth.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from 'react'
import { auth } from './firebase'
import {
    signInWithEmailAndPassword,
    createUserWithEmailAndPassword,
    signOut,
    onAuthStateChanged,
    GoogleAuthProvider,
    signInWithPopup,
    User,
} from 'firebase/auth'

// 컨텍스트 타입 정의
interface AuthContextType {
    user: User | null
    login: (email: string, password: string) => Promise<void>
    register: (email: string, password: string) => Promise<void>
    logout: () => Promise<void>
    loginWithGoogle: () => Promise<void>
}

// AuthContext 컨텍스트 생성
const AuthContext = createContext<AuthContextType>({
    user: null,
    login: async () => {},
    register: async () => {},
    logout: async () => {},
    loginWithGoogle: async () => {},
})

// AuthProvider 컴포넌트 정의
export const AuthProvider = ({ children }: { children: ReactNode }) => {
    const [user, setUser] = useState<User | null>(null)

    // 사용자 상태 업데이트
    useEffect(() => {
        const unsubscribe = onAuthStateChanged(auth, setUser)
        return () => unsubscribe()
    }, [])

    // 로그인 함수
    const login = async (email: string, password: string) => {
        await signInWithEmailAndPassword(auth, email, password)
    }

    // 회원가입 함수
    const register = async (email: string, password: string) => {
        await createUserWithEmailAndPassword(auth, email, password)
    }

    // 로그아웃 함수
    const logout = async () => {
        await signOut(auth)
    }

    // Google 로그인 함수
    const loginWithGoogle = async () => {
        const provider = new GoogleAuthProvider()
        try {
            await signInWithPopup(auth, provider)
        } catch (error) {
            console.error('Google 로그인 실패:', error)
        }
    }

    return (
        <AuthContext.Provider value={{ user, login, register, logout, loginWithGoogle }}>
            {children}
        </AuthContext.Provider>
    )
}

// 사용자 정의 훅
export const useAuth = () => useContext(AuthContext)

- 로그인 및 회원가입 기능 포함된 헤더

이제 로그인 및 회원가입 기능이 포함된 헤더를 만들어보겠습니다.

// components/Layout/Header.tsx
import Link from 'next/link'
import { useAuth } from '@/firebase/auth'

const Header = () => {
    const { user, loginWithGoogle, logout } = useAuth()

    return (
        <header>
            <h1>
                <Link href="/">logo</Link>
            </h1>

            <nav>
                <ul>
                    <li>
                        <Link href="/about">About</Link>
                    </li>
                    <li>
                        <Link href="/post">Post</Link>
                    </li>
                </ul>
            </nav>

            <div className="space-x-4">
                {user ? (
                    <button onClick={logout} className="p-2 bg-red-500 rounded">
                        로그아웃
                    </button>
                ) : (
                    <>
                        <button onClick={loginWithGoogle} className="p-2 bg-blue-500 rounded">
                            Google로 로그인
                        </button>
                        <Link href="/login">
                            <button className="p-2 bg-green-500 rounded">로그인</button>
                        </Link>
                        <Link href="/signup">
                            <button className="p-2 bg-yellow-500 rounded">회원가입</button>
                        </Link>
                    </>
                )}
            </div>
        </header>
    )
}

export default Header

- 로그인 페이지

// pages/login.tsx
import { useState } from 'react'
import { useRouter } from 'next/router'
import { useAuth } from '@/firebase/auth'

const Login = () => {
    const { login, loginWithGoogle } = useAuth()
    const router = useRouter()
    const [email, setEmail] = useState('')
    const [password, setPassword] = useState('')
    const [error, setError] = useState('')

    const handleLogin = async (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault()
        try {
            await login(email, password)
            router.push('/')
        } catch (err) {
            setError('로그인 실패: 잘못된 이메일 또는 비밀번호입니다.')
        }
    }

    return (
        <div>
            <h2>로그인</h2>
            {error && <p>{error}</p>}
            <form onSubmit={handleLogin}>
                <label>
                    이메일:
                    <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
                </label>
                <label>
                    비밀번호:
                    <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
                </label>
                <button type="submit">로그인</button>
            </form>
            <button onClick={loginWithGoogle}>Google로 로그인</button>
        </div>
    )
}

export default Login

- 회원가입 페이지

// pages/signup.tsx
// pages/register.tsx
import { useState } from 'react'
import { useRouter } from 'next/router'
import { useAuth } from '@/firebase/auth'

const Signup = () => {
    const { register } = useAuth()
    const router = useRouter()
    const [email, setEmail] = useState('')
    const [password, setPassword] = useState('')
    const [error, setError] = useState('')

    const handleRegister = async (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault()
        try {
            await register(email, password)
            router.push('/')
        } catch (err) {
            setError('회원가입 실패: 이메일 형식이 올바르지 않거나 비밀번호가 너무 짧습니다.')
        }
    }

    return (
        <div>
            <h2>회원가입</h2>
            {error && <p>{error}</p>}
            <form onSubmit={handleRegister}>
                <label>
                    이메일:
                    <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
                </label>
                <label>
                    비밀번호:
                    <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
                </label>
                <button type="submit">회원가입</button>
            </form>
        </div>
    )
}

export default Signup

- AuthProvider 감싸기

마지막으로 AuthProvider 컴포넌트를 모든 페이지에 적용하기 위해 pages/_app.tsx 파일을 다음과 같이 수정합니다:

// pages/_app.tsx
import { AppProps } from 'next/app'
import Layout from '../components/Layout/Layout'
import { AuthProvider } from '../src/firebase/auth'
import app from '../src/firebase/firebase' // Firebase SDK 구성을 위한 설정 파일 import
import '../styles/globals.css'

function MyApp({ Component, pageProps }: AppProps) {
    return (
        <AuthProvider>
            <Layout>
                <Component {...pageProps} />
            </Layout>
        </AuthProvider>
    )
}

export default MyApp

3. Firebase Firestore 설정

firebase firestore를 사용하여 데이터베이스를 생성하고, 데이터를 추가, 조회, 수정, 삭제하는 방법을 알아보겠습니다.

3.1. Firestore 데이터베이스 생성

  • Firebase Console에서 Firestore 메뉴로 이동합니다.
  • [데이터베이스 만들기] 버튼을 클릭합니다.
  • [시작하기] 버튼을 클릭합니다.
  • [보안 규칙] 설정을 테스트 모드로 설정합니다.
  • [다음] 버튼을 클릭합니다.
  • [데이터베이스 만들기] 버튼을 클릭합니다.

3.2. Firestore 데이터베이스 설정

  • Firestore 데이터베이스를 설정합니다.
// firebase/firebase.ts
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getFirestore } from 'firebase/firestore' // Firestore를 위한 import 추가

// Firebase 앱 설정
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,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

// Firebase 앱 초기화
const app = initializeApp(firebaseConfig)

// 인증 서비스 인스턴스 생성
export const auth = getAuth(app)

// Firestore 서비스 인스턴스 생성
export const db = getFirestore(app) // Firestore 인스턴스를 추가하고 export합니다.
// src/firebase/firestore.ts
import { db } from './firebase'
import { collection, getDocs, addDoc, doc, updateDoc, deleteDoc } from 'firebase/firestore'

// 추가 (Create)
export async function addDocument(collectionName: string, data: object) {
    try {
        const docRef = await addDoc(collection(db, collectionName), data)
        console.log('Document written with ID:', docRef.id)
    } catch (e) {
        console.error('Error adding document:', e)
    }
}

// 조회 (Read)
export async function getAllDocuments(collectionName: string) {
    const querySnapshot = await getDocs(collection(db, collectionName))
    querySnapshot.forEach((doc) => {
        console.log(`${doc.id} =>`, doc.data())
    })
}

// 수정 (Update)
export async function updateDocument(collectionName: string, docId: string, newData: object) {
    const docRef = doc(db, collectionName, docId)
    try {
        await updateDoc(docRef, newData)
        console.log('Document updated')
    } catch (e) {
        console.error('Error updating document:', e)
    }
}

// 삭제 (Delete)
export async function deleteDocument(collectionName: string, docId: string) {
    const docRef = doc(db, collectionName, docId)
    try {
        await deleteDoc(docRef)
        console.log('Document deleted')
    } catch (e) {
        console.error('Error deleting document:', e)
    }
}

3.4. Firestore 데이터베이스 추가

  • Firestore 데이터베이스를 사용하여 데이터를 추가, 조회, 수정, 삭제하는 방법을 알아보겠습니다.
// src/pages/new.tsx
import React, { useState } from 'react'
import { addDocument } from '@/firebase/firestore'

const AddPostPage: React.FC = () => {
    const [title, setTitle] = useState('')
    const [content, setContent] = useState('')

    // 폼 제출 시 Firestore에 데이터를 추가하는 함수 호출
    const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault()
        // 입력된 데이터 유효성 검사
        if (title.trim() === '' || content.trim() === '') {
            alert('제목과 내용을 입력하세요!')
            return
        }

        // Firestore에 문서 추가
        try {
            await addDocument('posts', { title, content })
            alert('새 게시물이 추가되었습니다!')
            setTitle('')
            setContent('')
        } catch (error) {
            console.error('게시물 추가 중 오류 발생:', error)
            alert('게시물 추가에 실패했습니다.')
        }
    }

    return (
        <div>
            <h1>게시물 추가하기</h1>
            <form onSubmit={handleSubmit}>
                <div>
                    <label htmlFor="title">제목:</label>
                    <input id="title" type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
                </div>
                <div>
                    <label htmlFor="content">내용:</label>
                    <textarea id="content" value={content} onChange={(e) => setContent(e.target.value)} />
                </div>
                <button type="submit">추가하기</button>
            </form>
        </div>
    )
}

export default AddPostPage

3.5. Firestore 데이터 수정

// src/components/PostEdit.tsx
import React, { useState, useEffect } from 'react'
import { updateDocument } from '@/firebase/firestore'

interface PostEditProps {
    id: string
    initialTitle: string
    initialContent: string
    onCancel: () => void
    onUpdate: () => void
}

const PostEdit: React.FC<PostEditProps> = ({ id, initialTitle, initialContent, onCancel, onUpdate }) => {
    const [title, setTitle] = useState(initialTitle)
    const [content, setContent] = useState(initialContent)

    const handleUpdate = async (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault()
        await updateDocument('posts', id, { title, content })
        onUpdate() // 성공 시 부모 컴포넌트에 알리기 위해 사용
    }

    return (
        <div>
            <h2>Edit Post</h2>
            <form onSubmit={handleUpdate}>
                <div>
                    <label htmlFor="title">Title:</label>
                    <input id="title" type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
                </div>
                <div>
                    <label htmlFor="content">Content:</label>
                    <textarea id="content" value={content} onChange={(e) => setContent(e.target.value)} />
                </div>
                <button type="submit">Update</button>
                <button type="button" onClick={onCancel}>
                    Cancel
                </button>
            </form>
        </div>
    )
}

export default PostEdit

3.7. Firestore 데이터 조회

데이터를 조회하고 수정, 삭제 버튼을 추가하여 데이터를 수정하거나 삭제할 수 있습니다.

// src/components/PostsList.tsx
// src/components/PostsList.tsx
import React, { useState, useEffect } from 'react'
import { db } from '@/firebase/firebase'
import { collection, getDocs, query, orderBy } from 'firebase/firestore'
import { deleteDocument } from '@/firebase/firestore'
import PostEdit from './PostEdit'

interface Post {
    id: string
    title: string
    content: string
}

const PostsList: React.FC = () => {
    const [posts, setPosts] = useState<Post[]>([])
    const [editingPostId, setEditingPostId] = useState<string | null>(null)

    // Firestore에서 모든 글을 가져와 상태로 설정
    const fetchPosts = async () => {
        const querySnapshot = await getDocs(query(collection(db, 'posts'), orderBy('title')))
        const postsData = querySnapshot.docs.map((doc) => ({
            id: doc.id,
            title: doc.data().title,
            content: doc.data().content,
        }))
        setPosts(postsData)
    }

    useEffect(() => {
        fetchPosts()
    }, [])

    // 글 삭제
    const handleDelete = async (id: string) => {
        await deleteDocument('posts', id)
        fetchPosts() // 목록을 다시 불러옵니다.
    }

    // 글 편집 완료
    const handleUpdate = () => {
        setEditingPostId(null)
        fetchPosts() // 업데이트된 목록을 불러옵니다.
    }

    return (
        <div>
            <h2>Posts List</h2>
            {editingPostId ? (
                <PostEdit
                    id={editingPostId}
                    initialTitle={posts.find((post) => post.id === editingPostId)?.title ?? ''}
                    initialContent={posts.find((post) => post.id === editingPostId)?.content ?? ''}
                    onCancel={() => setEditingPostId(null)}
                    onUpdate={handleUpdate}
                />
            ) : (
                <>
                    {posts.length > 0 ? (
                        <ul>
                            {posts.map((post) => (
                                <li key={post.id}>
                                    <h3>{post.title}</h3>
                                    <p>{post.content}</p>
                                    <button onClick={() => setEditingPostId(post.id)}>Edit</button>
                                    <button onClick={() => handleDelete(post.id)}>Delete</button>
                                </li>
                            ))}
                        </ul>
                    ) : (
                        <p>No posts found.</p>
                    )}
                </>
            )}
        </div>
    )
}

export default PostsList
// src/pages/post/index.tsx
import PostsList from '@/components/PostsList'

const PostsPage: React.FC = () => {
    return (
        <div>
            <h1>Posts</h1>
            <PostsList />
        </div>
    )
}

export default PostsPage

'Front > Next.js' 카테고리의 다른 글

Next.js 기초 및 설정  (1) 2024.05.05
티스토리 친구하기