Skip to content

Latest commit

ย 

History

History
228 lines (194 loc) ยท 7.78 KB

3_data_fetching.md

File metadata and controls

228 lines (194 loc) ยท 7.78 KB

3. DATA FETCHING

Introduction

  • client, server component๋กœ๋ถ€ํ„ฐ data๋ฅผ fetch ํ•˜๋Š” ๋ฒ•
  • streaming์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•
  • suspense๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•
  • loading fallback์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•
  • error boundary๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฒ•

API URL

https://nomad-movies.nomadcoders.workers.dev/

Client Side

client component์—์„œ data๋ฅผ fetch ํ•˜๋Š” ๋ฒ•

useEffect, useState๋ฅผ ์‚ฌ์šฉํ•ด data๋ฅผ fetch

export default function Home() {
    const [isLoading, setIsLoading] = useState(true);
    const [movies, setMovies] = useState([]);

    const getMovies = async () => {
        const response = await fetch('https://nomad-movies.nomadcoders.workers.dev/movies');
        const json = await response.json();
        setMovies(json);
        setIsLoading(false);
    };

    useEffect(() => {
        getMovies();
    }, []);

    return <div>{isLoading ? 'Loading...' : JSON.stringify(movies)}</div>;
}
  • client component์—์„œ๋Š” metadata๋ฅผ export ํ•  ์ˆ˜ ์—†์Œ
  • client์—์„œ fetch ํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ค˜์•ผ ํ•˜๋ฉฐ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ง์ ‘ ํ™•์ธํ•˜๊ณ  ๊ตฌํ˜„ํ•ด์•ผ ํ•จ
  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ API์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณด์•ˆ์— ์ทจ์•ฝํ•จ

Server Side

server component์—์„œ data๋ฅผ fetch ํ•˜๋Š” ๋ฒ•

useEffect, useState ์—†์ด data๋ฅผ fetch

const URL = 'https://nomad-movies.nomadcoders.workers.dev/movies';

async function getMovies() {
  await new Promise(resolve => setTimeout(resolve, 5000)); // ์‘๋‹ต์ด 5์ดˆ ๊ฑธ๋ฆฐ๋‹ค๊ณ  ๊ฐ€์ •
  const response = await fetch(URL);
  return await response.json();
}

export default async function Home() {
  const movies = await getMovies();
  return <div>{JSON.stringify(movies)}</div>;
}
  • ์ž๋™์œผ๋กœ fetch๋œ url์„ ์บ์‹ฑํ•จ
  • server์—์„œ fetchํ•ด๋„ ๋ฐฑ์—”๋“œ์˜ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ๊ฐ„์ด ๋ฐœ์ƒํ•˜๊ณ , ๋กœ๋”ฉ์ค‘์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž์—๊ฒŒ ์–ด๋– ํ•œ UI๋„ ๋…ธ์ถœ๋˜์ง€ ์•Š์Œ

์บ์‹œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

  1. fetch ์˜ต์…˜ ์‚ฌ์šฉํ•˜๊ธฐ
    • no-store๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ญ์ƒ ์ตœ์‹  ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
    • ๋งค ์š”์ฒญ๋งˆ๋‹ค API๋ฅผ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Œ
    const URL = 'https://nomad-movies.nomadcoders.workers.dev/movies';
    
    async function getMovies() {
    - const response = await fetch(URL);
    + const response = await fetch(URL, { cache: 'no-store' });
      return await response.json();
    }
    
    export default async function Home() {
      const movies = await getMovies();
      return <div>{JSON.stringify(movies)}</div>;
    }
  2. revalidate ๊ธฐ๋Šฅ ์‚ฌ์šฉํ•˜๊ธฐ
    • revalidate ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ์ • ์‹œ๊ฐ„๋งˆ๋‹ค ์บ์‹œ๋ฅผ ๊ฐฑ์‹ 
    • ์ดˆ ๋‹จ์œ„
    const URL = 'https://nomad-movies.nomadcoders.workers.dev/movies';
    
    async function getMovies() {
    - const response = await fetch(URL);
    + const response = await fetch(URL, { next: { revalidate: 60 } });
      return await response.json();
    }
    
    export default async function Home() {
    const movies = await getMovies();
    return <div>{JSON.stringify(movies)}</div>;
    }

Loading Components

server side์—์„œ fetchํ•˜๋Š” ๊ฒฝ์šฐ ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ ๋ Œ๋”๋ง ์ž‘์—…์ด ์ด๋ฃจ์–ด ์ง€์ง€ ์•Š๋Š”๋ฐ, ์ด๋•Œ ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๋กœ๋”ฉ UI๋ฅผ ๋…ธ์ถœ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ

loading component์˜ ์ž‘๋™ ๋ฐฉ์‹

  • ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ๋กœ๋”ฉ UI๋ฅผ ๋…ธ์ถœํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ๋ฐฑ์—”๋“œ์—์„œ ๋กœ๋”ฉ์ค‘์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ €๋Š” ๋ฐฑ์—”๋“œ ์ž‘์—…์ด ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜๋‹ค๊ณ  ์ƒ๊ฐํ•จ
  • streaming์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ Next๋Š” ํŽ˜์ด์ง€์˜ HTML์„ ์ฒญํฌ๋กœ ๋‚˜๋ˆ„์–ด ๋จผ์ € ์ค€๋น„๋œ ์ฒญํฌ๋ฅผ ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ๋ฐ˜ํ™˜ํ•˜๊ณ  await์ด ๋๋‚˜๋ฉด client์—์„œ loading component๋ฅผ await ๋œ component๋กœ ๊ต์ฒด
  • UI๊ฐ€ ๋ Œ๋”๋ง ๋˜๊ธฐ ์ „์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ํŽ˜์ด์ง€์˜ ์ผ๋ถ€๋ฅผ ๋น ๋ฅด๊ฒŒ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ์Œ

loading component ์‚ฌ์šฉ ๋ฒ•

  • ํŒŒ์ผ ๋ช…์€ ํ•ญ์ƒ loading์ด์–ด์•ผ ํ•จ
  • pageํŒŒ์ผ๊ณผ ๋™์ผํ•œ ๊ฒฝ๋กœ์— ์žˆ์–ด์•ผ ํ•จ

Parallel Requests

์ง๋ ฌ ์š”์ฒญ ๋ฐฉ์‹

async function getMovie(id: string) {
   console.log(`Fetching movie: ${Date.now()}`);
   await new Promise(resolve => setTimeout(resolve, 5000)); // ์‘๋‹ต์ด 5์ดˆ ๊ฑธ๋ฆฐ๋‹ค๊ณ  ๊ฐ€์ •
   const response = await fetch(`${API_URL}/${id}`);
   return response.json();
}

async function getVideos(id: string) {
   console.log(`Fetching videos: ${Date.now()}`);
   await new Promise(resolve => setTimeout(resolve, 5000)); // ์‘๋‹ต์ด 5์ดˆ ๊ฑธ๋ฆฐ๋‹ค๊ณ  ๊ฐ€์ •
   const response = await fetch(`${API_URL}/${id}/videos`);
   return response.json();
}

export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
   console.log('start fetching');
   const movie = await getMovie(id);
   const videos = await getVideos(id);
   console.log('end fetching');
}

output

  • movie, videos fetch๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋จ
  • ์š”์ฒญ ์‹œ๊ฐ„ ์ด 10์ดˆ + API ์‘๋‹ต ์‹œ๊ฐ„ -> ์ตœ์†Œ 10์ดˆ ์ด์ƒ ์†Œ์š”
start fetching
Fetching movie: 1717382782714
Fetching videos: 1717382788223
end fetching
GET /movies/653346 200 in 11006ms

๋ณ‘๋ ฌ ์š”์ฒญ ๋ฐฉ์‹

export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
  console.log('start fetching');
- const movie = await getMovie(id);
- const videos = await getVideos(id);
+ const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
  console.log('end fetching');
}

output

  • movie, videos fetch๊ฐ€ ๋™์‹œ์— ์‹คํ–‰๋จ
  • ์š”์ฒญ ์‹œ๊ฐ„ ์ด 5์ดˆ + API ์‘๋‹ต ์‹œ๊ฐ„ -> ์ตœ์†Œ 5์ดˆ ์ด์ƒ ์†Œ์š”
start fetching
Fetching movie: 1717383063742
Fetching videos: 1717383063742
end fetching
GET /movies/653346 200 in 5528ms

Suspense

Suspense๋กœ ๋ณ‘๋ ฌ ์š”์ฒญ์„ ๋ถ„๋ฆฌํ•ด์„œ ๋ชจ๋“  ์‘๋‹ต์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ์‘๋‹ต์ด ๋๋‚œ ๋ฐ์ดํ„ฐ๋ถ€ํ„ฐ ๋ฐ”๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ

  1. ๊ฐ ์š”์ฒญ์„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌ
    MovieInfo
    • movie info์— ๊ด€ํ•œ ๋ฐ์ดํ„ฐ๋งŒ fetch
    async function getMovie(id: string) {
      console.log(`Fetching movie: ${Date.now()}`);
      await new Promise(resolve => setTimeout(resolve, 5000)); // ์‘๋‹ต์ด 5์ดˆ ๊ฑธ๋ฆฐ๋‹ค๊ณ  ๊ฐ€์ •
      const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/${id}`);
      return response.json();
    }
    
    export default async function MovieInfo({ id }: { id: string }) {
      const movie = await getMovie(id);
      return <h6>{JSON.stringify(movie)}</h6>;
    }
    MovieVideos
    • movie videos์— ๊ด€ํ•œ ๋ฐ์ดํ„ฐ๋งŒ fetch
    async function getVideos(id: string) {
       console.log(`Fetching videos: ${Date.now()}`);
       await new Promise(resolve => setTimeout(resolve, 3000)); // ์‘๋‹ต์ด 3์ดˆ ๊ฑธ๋ฆฐ๋‹ค๊ณ  ๊ฐ€์ •
       const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/${id}/videos`);
       return response.json();
    }
    
    export default async function MovieVideos({ id }: { id: string }) {
       const videos = await getVideos(id);
       return <h6>{JSON.stringify(videos)}</h6>;
    }
  2. ์ƒ์œ„์—์„œ ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ Suspense๋กœ wrappingํ•˜๊ณ  fallback ์ง€์ •
    import MovieVideos from '../../../../components/movie-videos';
    import MovieInfo from '../../../../components/movie-info';
    import { Suspense } from 'react';
    
    export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
       return (
         <div>
            <Suspense fallback={<h1>Loading movie info</h1>}>
               <MovieInfo id={id} />
            </Suspense>
            <Suspense fallback={<h1>Loading movie videos</h1>}>
               <MovieVideos id={id} />
            </Suspense>
         </div>
       );
    }

Error Handling

error.tsx ํŒŒ์ผ๋กœ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ๋ณด์—ฌ์งˆ UI๋ฅผ ๋…ธ์ถœ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ

error handling ๋ฐฉ๋ฒ•

  • ํŒŒ์ผ ๋ช…์€ ํ•ญ์ƒ error์—ฌ์•ผ ํ•จ
  • page ํŒŒ์ผ๊ณผ ๋™์ผํ•œ ๊ฒฝ๋กœ์— ์žˆ์–ด์•ผ ํ•จ