ํฐ์คํ ๋ฆฌ ๋ทฐ
[NEXTFLIX_Day3] ํ์ด์ง๋ค์ด์ (2) - ๋์ ๋ผ์ฐํ ์๋ ๋ฐฉ์
์ฑ._. 2025. 3. 24. 22:08ํ์ด์ง๋ค์ด์ (1)์์ ์๋ชป๋ searchParams์ ์ฌ์ฉ์ผ๋ก API ์๋ํฌ์ธํธ๊ฐ ์๋ ํ์ฌ ํ์ด์ง์ ์ ๊ทผํ๋ค.
[NEXTFLIX_Day3] ํ์ด์ง๋ค์ด์ (1) - ๊ธฐ๋ฅ ํ์
์นดํ ๊ณ ๋ฆฌ๋ณ ์ํ ์์ธํ์ด์ง์์ ํ์ด์ง๋ค์ด์ ์ ๊ตฌํํ๋ ค๊ณ ํ๋ค.์ถํ์ ์ด๋ป๊ฒ ํ์ฉ๋ ์ง ๋ชจ๋ฅด๋, ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ๊น์ง์ ํธ๋ฌ๋ธ ์ํ ์ ์์ธํ ๊ธฐ๋กํ๊ณ ์ ํ๋ค.์ด๋ฒ ๊ธ์๋ ์๋ชป๋ ๋ฐฉํฅ์ฑ
coco910.tistory.com
๊ตฌํํ๊ณ ์ ํ๋ ๋ฐฉํฅ์ ๋ง๋ ๋ฐฉ๋ฒ์ธ '๋์ ๋ผ์ฐํธ ๊ตฌ์กฐ'๋ฅผ ์ ์ฉํ๋ฉฐ ์๋ ๋ฐฉ์์ ํจ๊ป ์ ๋ฆฌํด๋ณด๊ณ ์ ํ๋ค.
์ถ๊ฐ๋ก ํ์ด์ง์ ๋ฒํผ์ ๋ก์ง์ ์ดํด๋ณด๋ฉฐ ๋์ ํ๋ฆ๋ ํ์ ํด๋ณธ๋ค.
ํ์ด์ง๋ค์ด์ ๋ฒํผ์ ๊ธฐ๋ฅ
๋ค์ ์ง์ด๋ณด๋ ๋ฒํผ์ ๊ธฐ๋ฅ
- ๊ธฐ๋ฅ 1 : Page = 1 ๊ธฐ๋ณธ๊ฐ
- ๊ธฐ๋ฅ 2 : ๋ฒํผ ํด๋ฆญ ๋ง๋ค -1 / +1
- ๊ธฐ๋ฅ 3 : ์ฒซ ํ์ด์ง : ‘์ด์ ’ ๋ฒํผ ๋นํ์ฑํ / ๋ง์ง๋ง ํ์ด์ง: ‘๋ค์’ ๋ฒํผ ๋นํ์ฑํ
Next.js์ ๋์ ๋ผ์ฐํธ ์ฒ๋ฆฌ ๋ฐฉ์

์์ ๊ฐ์ ํ์ผ ๊ตฌ์กฐ๋ ๋์ ์ผ๋ก path ํ๋ผ๋ฏธํฐ๋ก ๋ค์ด์ค๋ ๊ฐ์ด [ ] ์์ ๋ด๊ธฐ๊ฒ ๋๋ค.
// app > api > category > now-playing > [page] > page.tsx
const nowPlayingPage = async ({ params }: { params: { page?: string } }) => {}
๋ด๋ถ ํ์ด์ง์ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ์ด๋ค.
์ฆ,
http://localhost:3000/category/now-playing/2
์ ๊ตฌ์กฐ์์ /2 ๋ถ๋ถ์ด ๊ณ ์ ๋์ง ์๊ณ ๊ณ์ํด์ ๋ณํ๋ ๊ฐ์ผ๋ก ๋ค์ด์ค๋ ๊ฒ์ด๋ค.
๊ทธ ๊ฐ์ ํ์ด์ง ์ปดํฌ๋ํธ์ ์ธ์๋ก ๋ค์ด์ค๊ฒ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด, ์ธ์๋ก๋ ๋ด๊ฐ ์ค์ ํด ๋ page๊ฐ ์๋ params๊ฐ ๋ค์ด์ค๋ ์ด์ ๊ฐ ๋ญ๊น?
๊ทธ๊ฑด Next.js ์์ ์ ํ ๊ท์น๊ณผ ์ฐ๊ด์ด ์๋ค.
๋ฅ์คํธ์์๋ [ ]์ ๊ฐ์ ๋์ ์ธ๊ทธ๋จผํธ๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋น ๊ฐ์ด ์๋์ ์ผ๋ก params ๊ฐ์ฒด ์์ ๋ค์ด๊ฐ๋ค.

Next.js ํ๊ธ ๋ฌธ์ - ๋์ ๋ผ์ฐํ
์๋ฅผ ๋ค์ด, category/now-playing/[page] ๊ฒฝ๋ก์์ /category/now-playing/2๋ก ์ ๊ทผํ๋ฉด
params = { page: "2" } ์ ๊ฐ์ ํํ๋ก ๋ด๊ธด๋ค.
๋ฐ๋ผ์ params.page๋ก page ๊ฐ์ ์ ๊ทผํ ์ ์๋ค.
์ ๊ตณ์ด ๊ฐ์ฒด์ ๋ด๋ ๊ฑธ๊น?
์ง๊ธ๊ณผ ๊ฐ์ ๋จ์ผ ๊ฒฝ๋ก ์ธ์ ๋ค์ค ๊ฒฝ๋ก๊ฐ ์ ์ฉ๋ ๋ ํ์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋์ ๋ผ์ฐํธ๊ฐ ๋ ๊ฐ ์ด์ ์ฌ์ฉ๋ ๋ params์๋ ๊ฐ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ด๊ธฐ๊ฒ ๋๋ค.
/category/[genre]/[page] ๐๐ป params = { genre: "action", page: "3" }
์ถ๊ฐ๋ก ๋์ ๊ฒฝ๋ก ๊ฐ์ ๋ฌธ์์ด(string)์ผ๋ก ์ ๋ฌํ๋ ๊ฒ์ด ๋ฅ์คํธ์ ๊ท์น์ด๋ค.
๊ทธ๊ฒ์ด ๋ฅ์คํธ๋๊น..
[page] ์ธ ์ด์
์ฒ์ ๋์ ๋ผ์ฐํธ ๊ฐ๋ ์ ๋ํด์ ๋ฐฐ์ธ ๋๋ [id]๋ก ์ฌ์ฉํ๋๋ฐ ์ด๋ฒ์๋ [page]๋ก ์ฌ์ฉํ ์ด์ ๋ฅผ ๊ฐ๋จํ ์ ๋ฆฌํด๋ณด์๋ฉด,
id๋ ๋ณดํต ํน์ ํ ์ด๋ค ๊ฒ์ ์ง์นญํ๊ฑฐ๋ ์๋ณํ ๋ ์ฌ์ฉํ๋ค.
ํ์ฌ ์ํฉ์์๋ ํ์ด์ง๋ค์ด์ (ํ์ด์ง ์ด๋)์ด ๋ชฉ์ ์ด๊ธฐ ๋๋ฌธ์ page๋ก ๋์๋ค.
๋ํ ๋ณ์๋ช ์ a, b ๋ฑ์ด ์๋ NowPlayingPage์ฒ๋ผ ์๊ธฐ ์ฝ๊ฒ ์ง๋ฏ์ด ํ์ด์ง ๋ฒํธ์์ ์ง๊ด์ ์ผ๋ก ์ ์ ์๋๋ก ์ค์ ํ์๋ค.
ํ์ด์ง์ ๋ฒํผ์ ๊ด๊ณ
ํ์ด์ง๋ค์ด์ ๋ก์ง์ ํ๋ฆ์ ์ ์ด๋ณด์๋ฉด,
- ํด๋น ํ์ด์ง๋ ์ธ์๋ก params ๊ฐ์ฒด๋ฅผ ๋ฐ์, params์ page ๊ฐ์ ์ซ์๋ก ๋ณํํ์ฌ ์๋ฒ ์ก์ ์ธ์๋ก ์ ๋ฌํ๋ค.
- ์๋ฒ ์ก์ ์ ๋ฐ์ ์ธ์ ๊ฐ(page)์ API ์๋ํฌ์ธํธ์ ์ ์ฉํ๋ค.
- ํด๋น ํ์ด์ง์์ ์๋ฒ ์ก์ ํจ์๋ฅผ ์คํ(์์ฒญ)ํ๋ค.
- API ์๋ํฌ์ธํธ์ ํด๋นํ๋ ์๋ต ๊ฐ์ ์๋ฒ ์ก์ ํจ์ return์ ๋ด๋๋ค.
- ๊ทธ ์๋ต๊ฐ์ ํด๋น ํ์ด์ง์์ ๋ณ์์ ๋ด๋๋ค.
- ์๋ต๊ฐ(๋ณ์)์ผ๋ก map์ ๋๋ ค์ ์ํ๋ ๊ฐ์ ์ฐ์ด๋ธ๋ค.
- ๋ฒํผ ์ปดํฌ๋ํธ์ ํ์ํ ๊ฐ์ props๋ก ์ ๋ฌํ๋ค.
- ๋ฒํผ ์ปดํฌ๋ํธ๋ useRouter() ํ ์ ์ฌ์ฉํ์ฌ ๋ผ์ฐํธ๋ฅผ ๋ณ๊ฒฝํ๋ค.
- ๋ฒํผ ์ปดํฌ๋ํธ๋ ๋ฐ์ ๊ฐ๋ค์ ์ด์ฉํด์ ์ด์ , ๋ค์ ๋ฒํผ์ ํ์ฑํํ๋ค.
๐์๋ฒ ์ก์
// services > category > surverApi.ts
'use server';
import { ApiResponse } from '@/types/category/movie';
import { TMDB_BASE_URL } from '@/constants/tmdbBaseUrl';
const options = {
method: 'GET',
headers: {
accept: 'application/json',
Authorization: `Bearer ${process.env.TMDB_API_KEY}`,
},
};
// NowPlaying
export const getNowPlaying = async (page: number = 1): Promise<ApiResponse> => {
const TMDB_NOW_PLAYING_API = `${TMDB_BASE_URL}/now_playing?language=ko&page=${page}®ion=KR`;
const res = await fetch(TMDB_NOW_PLAYING_API, options);
const data = await res.json();
return data;
};
๐ํ์ด์ง ์ปดํฌ๋ํธ
- ์๋ฒ ์ปดํฌ๋ํธ (ํ ์ฌ์ฉ ๋ชป ํจ)
- ์๋ฒ ์ก์ ์ ํธ์ถํ์ฌ ์๋ต ๊ฐ ๋ฐ์
- ์๋ต ๊ฐ์ผ๋ก ์ํ ์นด๋ ๋ฆฌ์คํธ๋ฅผ ๋ฟ๋ฆผ
- ์๋ต ๊ฐ์์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฒํผ ์ปดํฌ๋ํธ๋ก ์ ๋ฌ
- ํ์ด์ง๋ค์ด์ ๋ฒํผ์ ํฌํจ
- ๋ณ๋๋๋ params๋ฅผ ์๋ฒ ์ก์ ์ ์ ๋ฌ
// app > category > now-playing > [page] > page.tsx
import PageNationBtn from '@/components/pageNationBtn';
import { Movie } from '@/types/category/movie';
import { getNowPlaying } from '@/utils/tmdb/serverApi';
import React from 'react';
// ์๋ฒ ์ปดํฌ๋ํธ์์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ๊ฐ์ ธ์ค๊ธฐ
const nowPlayingPage = async ({ params }: { params: { page: null | string } }) => {
const page = parseInt(params.page || '1'); // ์ซ์๋ก ๋ณํ & ๊ธฐ๋ณธ๊ฐ=1
const data = await getNowPlaying(page);
return (
<>
<div>
<h1>ํ์ด์ง {page}</h1>
{data.results.map((movie: Movie) => {
return <div key={movie.id}>{movie.title}</div>;
})}
</div>
<PageNationBtn page={page} totalPages={data.total_pages} basePath={'category/now-playing'} />
</>
);
};
export default nowPlayingPage;
๐๋ฒํผ ์ปดํฌ๋ํธ
- ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ (ํ ์ฌ์ฉ ๊ฐ๋ฅ)
- ๋ฒํผ ํด๋ฆญ ์ ๋์ ๋ผ์ฐํธ params ๋ณ๋์ํค๊ธฐ(+1 / -1)
- ๋ณ๋์ํจ ๋ผ์ฐํธ ์ ์ฉ(router.push)
- ์ ์ฒด ํ์ด์ง์ ๊ฐ๊ณผ ๋น๊ตํด์ '์ด์ ', '๋ค์' ๋ฒํผ ๋นํ์ฑํ
// components > pageNationBtn.tsx
'use client';
import { useRouter } from 'next/navigation';
interface PageNationProps {
page: number;
totalPages: number;
basePath: string;
}
const PageNationBtn = ({ page, totalPages, basePath }: PageNationProps) => {
const router = useRouter();
const goToPage = (newPage: number) => {
if (newPage < 1 || newPage > totalPages) return;
router.push(`/${basePath}/${newPage}`); // ํ์คํ ๋ฆฌ ์คํ(๋ฐฉ๋ฌธ ๊ธฐ๋ก)์ ์ถ๊ฐ
};
return (
<div className='flex gap-8'>
{page > 1 && <button onClick={() => goToPage(page - 1)}>์ด์ </button>}
<p>
ํ์ด์ง {page} / {totalPages}
</p>
{page < totalPages && <button onClick={() => goToPage(page + 1)}>๋ค์</button>}
</div>
);
};
export default PageNationBtn;
'Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [์ฌ๋๋ณ_Day3] ์บ๋ฆฐ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋น๊ต๐ (0) | 2025.04.01 |
|---|---|
| [์ฌ๋๋ณ_Day2] ํต์ฌ ๊ธฐ๋ฅ ๋ถ์ (0) | 2025.04.01 |
| [NEXTFLIX_Day3] ํ์ด์ง๋ค์ด์ (1) - ๊ธฐ๋ฅ ํ์ (1) | 2025.03.24 |
| [NEXTFLIX_Day2] ๊ตฌ์กฐํ ๋ฐ ์ ์ (5) | 2025.03.22 |
| [LoL info App_Final] LoL ์ ๋ณด ํ์ด์ง (0) | 2025.03.19 |