ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

ํŽ˜์ด์ง€๋„ค์ด์…˜(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์ฒ˜๋Ÿผ ์•Œ๊ธฐ ์‰ฝ๊ฒŒ ์ง“๋“ฏ์ด ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ์ž„์„ ์ง๊ด€์ ์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•˜์˜€๋‹ค.

 

 

ํŽ˜์ด์ง€์™€ ๋ฒ„ํŠผ์˜ ๊ด€๊ณ„

ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋กœ์ง์˜ ํ๋ฆ„์„ ์ ์–ด๋ณด์ž๋ฉด,

  1. ํ•ด๋‹น ํŽ˜์ด์ง€๋Š” ์ธ์ž๋กœ params ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„, params์˜ page ๊ฐ’์„ ์ˆซ์ž๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์„œ๋ฒ„ ์•ก์…˜ ์ธ์ž๋กœ ์ „๋‹ฌํ•œ๋‹ค.
  2. ์„œ๋ฒ„ ์•ก์…˜์€ ๋ฐ›์€ ์ธ์ž ๊ฐ’(page)์„ API ์—”๋“œํฌ์ธํŠธ์— ์ ์šฉํ•œ๋‹ค.
  3. ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ ์„œ๋ฒ„ ์•ก์…˜ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰(์š”์ฒญ)ํ•œ๋‹ค.
  4. API ์—”๋“œํฌ์ธํŠธ์— ํ•ด๋‹นํ•˜๋Š” ์‘๋‹ต ๊ฐ’์„ ์„œ๋ฒ„ ์•ก์…˜ ํ•จ์ˆ˜ return์— ๋‹ด๋Š”๋‹ค.
  5.  ๊ทธ ์‘๋‹ต๊ฐ’์„ ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ ๋ณ€์ˆ˜์— ๋‹ด๋Š”๋‹ค.
  6. ์‘๋‹ต๊ฐ’(๋ณ€์ˆ˜)์œผ๋กœ map์„ ๋Œ๋ ค์„œ ์›ํ•˜๋Š” ๊ฐ’์„ ์ฐ์–ด๋‚ธ๋‹ค.
  7. ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ์— ํ•„์š”ํ•œ ๊ฐ’์„ props๋กœ ์ „๋‹ฌํ•œ๋‹ค.
  8. ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋Š” useRouter() ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ผ์šฐํŠธ๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค.
  9. ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฐ›์€ ๊ฐ’๋“ค์„ ์ด์šฉํ•ด์„œ ์ด์ „, ๋‹ค์Œ ๋ฒ„ํŠผ์„ ํ™œ์„ฑํ™”ํ•œ๋‹ค.

 

๐Ÿ“์„œ๋ฒ„ ์•ก์…˜

// 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}&region=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;

 

 

 

 

๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
TAG
more
ยซ   2026/03   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ