๐Ÿ“บ Next.js ๋กœ Netflix ์›น์•ฑ ํด๋ก ์ฝ”๋”ฉ ๐ŸŽฌ

SeonDalยท2022๋…„ 11์›” 13์ผ
15

ย ๐ŸŒฟ ํšŒ๊ณ ๋ก

๋ชฉ๋ก ๋ณด๊ธฐ
4/7
post-thumbnail

<React๋งŒ ํ•ด๋ณธ ์ดˆ์งœ์˜ Next.js์™€ Typescript๋ฅผ ์ด์šฉํ•œ Netflix ์›น์•ฑ ํด๋ก ์ฝ”๋”ฉ>
NextJS์—์„œ GetServerSideProps๋ฅผ ์ด์šฉํ•˜์—ฌ ๊น”์Œˆํ•˜๊ฒŒ API์™€ ํ†ต์‹ ํ•˜๊ณ  Data Fetch๊นŒ์ง€ ํ•ด๋ณด์ž
(feat. <style jsx>)

๊ฒฐ๊ณผ๋ฌผ
https://next-netflix-16th-ten.vercel.app/



๐Ÿšจ Next.js Error Resolution

Next.js ๊ฐ€ React.js ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ๋งŒํผ ๊ทธ์ € ๋‹น์—ฐํ•˜๊ฒŒ ์ด์ „์— ๋ฆฌ์•กํŠธ๋กœ ๊ฐœ๋ฐœํ•  ๋•Œ์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์งฐ๋Š”๋ฐ ์ •์ฒด๋ฅผ ์•Œ ์ˆ˜ ์—†๋Š” ์—ฌ๋Ÿฌ ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ฒผ๋‹ค. ํ”„๋กœ์ ํŠธ์— Next.js ๋ฅผ ์ ์šฉํ•  ๋•Œ Next.js ์˜ ํŠน์„ฑ์„ ์ž˜ ๊ณ ๋ คํ•ด๋ณด์ž..

_

1. Data Fetching ์˜ค๋ฅ˜

ํ‰์†Œ์ฒ˜๋Ÿผ axios ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ํŽ˜์นญํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ด๊ฒƒ์ €๊ฒƒ ์˜ค๋ฅ˜๊ฐ€ ์ž๊พธ ์ƒ๊ฒผ๊ณ , ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด @transtack/react-query ์—์„œ Hydrate, QueryClient... ๋“ฑ ์—ฌ๋Ÿฌ๊ฐ€์ง€๋ฅผ ์ž„ํฌํŠธ์—์„œ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์žˆ์—ˆ๋‹ค.

๊ฐœ์ธ์ ์œผ๋กœ ์„ ํ˜ธํ•˜์ง€ ์•Š๋Š” ๋ฐฉ์‹์ด๊ธฐ์— ๋‚˜๋Š” ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—†์ด ๋‹ด๋ฐฑํ•˜๊ฒŒ Next.js ์—์„œ ์„œ๋ฒ„์—ฐ๊ฒฐ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํƒํ–ˆ๋‹ค.

๋ณธ ํฌ์ŠคํŒ…์—์„œ GetServerSideProps ๋ฅผ ๋‹ค๋ฃฌ ๋ถ€๋ถ„์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š” !

_

2. styled-components ์˜ค๋ฅ˜

๋ถ„๋ช… ์ฒ˜์Œ ๋นŒ๋“œํ•  ๋•Œ๋Š” ์˜ˆ์˜๊ฒŒ ๋นŒ๋“œ๋œ ํŽ˜์ด์ง€๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋งŒ ๋ˆ„๋ฅด๋ฉด ์Šคํƒ€์ผ์ด ์ ์šฉ์ด ํ’€๋ ธ๋‹ค.
์ •์ฒด๋ฅผ ์•Œ ์ˆ˜ ์—†๋Š” ๊ฒฝ๊ณ ๋„ ํ•จ๊ป˜..!

Warning: Prop className did not match. Server: "~" Client: "~"

๋ณธ ์˜ค๋ฅ˜๋Š” Nextjs ์˜ ํŠน์„ฑ๋•Œ๋ฌธ์— ์ƒ๊ธด ์˜ค๋ฅ˜์˜€๋‹ค.

Next.js ์—์„œ๋Š” SSR(Server Side Rendering)๋กœ pre-rendering ๋  ๋•Œ ์„œ๋ฒ„์—์„œ html ํŒŒ์ผ์„ ๊ตฌ์„ฑํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ €์— ์ „๋‹ฌํ•˜์—ฌ css ์™€ ํ•จ๊ป˜ ๋ Œ๋”๋ง ๋œ ํ›„์— JS ํŒŒ์ผ์ด ๋กœ๋“œ๋˜์–ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๊ฐ€ ์ ์šฉ๋œ๋‹ค.

๋”ฐ๋ผ์„œ JS ํŒŒ์ผ ๋‚ด์— CSS๋ฅผ ๋„ฃ์–ด์„œ ์ž‘์—…ํ•˜๋Š” styled-component๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ƒˆ๋กœ๊ณ ์นจํ• ๋•Œ ํ•ด๋‹น JS๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š๊ณ  ํ”„๋ฆฌ๋ Œ๋”๋ง๋œ html๋งŒ ๋ƒ…๋‹ค ๋ Œ๋”๋ง ๋˜์–ด๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค.


์‚ฌ์‹ค ๊ฒ€์ƒ‰ํ•ด๋ณด๋ฉด next.js ์—์„œ styled-component๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹์ด ์—ฌ๋Ÿฌ๊ฐœ ๋‚˜์˜จ๋‹ค.
(babel ์„ค์น˜, _document.tsx ์ถ”๊ฐ€ ๋“ฑ๋“ฑ..)

๊ทธ๋ ‡์ง€๋งŒ.. Next.js์˜ ๊ฐ€์žฅ ํฐ ์žฅ์ ์ค‘ ํ•˜๋‚˜๊ฐ€ SSR ์ธ๋ฐ ๊ตณ์ด ์ด ํŠน์ง•๊ณผ ์ถฉ๋Œ๋˜๋Š” ๋ฌด์–ธ๊ฐ€๋ฅผ ํ•˜๊ณ ์‹ถ์ง€ ์•Š์•˜๋‹ค.

  • Next.js ๋ฅผ ์ ์šฉํ•œ ํ”„๋กœ์ ํŠธ์—์„œ,
  • Styled-component ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด์„œ
  • jsx(๋˜๋Š” tsx) ํŒŒ์ผ ๋‚ด์—์„œ css๋กœ ์Šคํƒ€์ผ๋ง ํ•  ์ˆ˜ ์žˆ๋”ฐ๋Š” styled-component ์˜ ์žฅ์ ๋„ ๊ฐ€์ง€๋Š”

ํ•ด๊ฒฐ์ฑ…์„ ์ฐพ์•„๋ƒˆ๋‹ค.

๋ณธ ํฌ์ŠคํŒ…์—์„œ "style jsx" ๋ฅผ ๋‹ค๋ฃฌ ๋ถ€๋ถ„์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”

_

3. vercel ๋ฐฐํฌ ์˜ค๋ฅ˜

์™œ์ธ์ง€ ๋ฐฐํฌ๊ฐ€ ์•ˆ๋˜์—ˆ๋‹ค.... ์•Œ๊ณ ๋ณด๋‹ˆ " ๋นŒ๋“œ๊ฐ€ ์ž˜ ๋˜๋”๋ผ๋„ " ํ”„๋กœ์ ํŠธ ๋‚ด์— ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ์ƒ๊ธธ ์—ฌ์ง€๊ฐ€ ์กฐ๊ธˆ์ด๋ผ๋„ ์žˆ๋‹ค๋ฉด ๋ฐฐํฌ๊ฐ€ ์•ˆ๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

๋‚˜์˜ ๊ฒฝ์šฐ ํ”„๋กœ์ ํŠธ์—์„œ Typescript ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋ช‡๊ฐ€์ง€ props์— ํƒ€์ž…์„ ์ง€์ •ํ•˜์ง€ ์•Š์•„์„œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ๋„์› ๋Š”๋ฐ ๊ทธ๊ฑฐ ๋•Œ๋ฌธ์— ๋ฐฐํฌ๊ฐ€ ์•ˆ๋œ ๊ฒƒ์ด์˜€๋‹ค.

React, Next ํ”„๋กœ์ ํŠธ์—์„œ Typescript ์˜ค๋ฅ˜์—†์ด ์‚ฌ์šฉํ•˜๊ธฐ

๋ฐฐํฌ์ „์—๋Š” ๊ผผ๊ผผํ•˜๊ฒŒ ํ™•์ธํ•˜์ž...



โœจ Main Feat

1. SSR ์„ ์ด์šฉํ•œ Data Fetch

๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๋งŽ์ด ์•Œ๋ ค์ง„ The Movie Database API ๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

(1) API URL ๊ด€๋ฆฌ

api url๋“ค์„ ๊ด€๋ฆฌํ•˜๋Š” ํŒŒ์ผ์— ํ•„์š”ํ•œ url๋“ค์„ export ํ•ด๋†“์ž.

// api.tsx
import { API_KEY } from "./assets/config";

const BASE_URL = `https://api.themoviedb.org/3/movie/`;

export const getNowPlaying = `${BASE_URL}/now_playing?api_key=${API_KEY}&language=en-US&page=1`;
export const getTopRated = `${BASE_URL}/top_rated?api_key=${API_KEY}&language=en-US&page=1`;
export const getPopular = `${BASE_URL}/popular?api_key=${API_KEY}&language=en-US&page=1`;
export const getUpComing = `${BASE_URL}/upcoming?api_key=${API_KEY}&language=en-US&page=1`;
export const getTopSearches = `${BASE_URL}/top_rated?api_key=${API_KEY}`;

(2) getServerSideProps()

async ์™€ await์„ ์ ์ ˆํžˆ ์ด์šฉํ•˜์—ฌ data๋ฅผ fetch ํ•ด์ค€๋‹ค.
๋ณธ ํ•จ์ˆ˜์—์„œ return ํ•˜๋ฉด fetch ๋œ ๋ฐ์ดํ„ฐ๋“ค์„ props ๊ฐ์ฒด๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค!

// home.tsx
import { getNowPlaying, getTopRated, getPopular, getUpComing } from "../api";

...

export const getServerSideProps = async () => {
  const nowPlayingResponse = await (await fetch(getNowPlaying)).json();
  const nowPlayingMovies = nowPlayingResponse.results;

  const topRatedResponse = await (await fetch(getTopRated)).json();
  const topRatedMovies = topRatedResponse.results;

  const popularResponse = await (await fetch(getPopular)).json();
  const popularMovies = popularResponse.results;

  const upComingResponse = await (await fetch(getUpComing)).json();
  const upComingMovies = upComingResponse.results;
  return {
    props: {
      nowPlayingMovies,
      topRatedMovies,
      popularMovies,
      upComingMovies,
    },
  };
};

(3) props๋กœ ๋ฐ›์•„์™€์„œ ์‚ฌ์šฉํ•˜๊ธฐ

props๋กœ ์•„๊นŒ ๋ฆฌํ„ดํ•ด์ค€ ๊ฐ์ฒด๋“ค์„ ๋ฐ›์•„์˜จ๋‹ค.
๋ฐ์ดํ„ฐ๋“ค์„ ์ „๋‹ฌ๋ฐ›์€ props๋ฅผ ์‚ฌ์šฉํ•˜๋“ฏ์ด ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋!

โš ๏ธ Typescript๋ฅผ ์“ฐ๋Š”๊ฒฝ์šฐ props์˜ ํƒ€์ž…์„ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ •ํ•ด์ฃผ๋Š”๊ฒƒ์„ ์žŠ์ง€ ๋ง์ž !

// home.tsx
import { IMovieInfo } from "../interface";

...

// HomeProps๋Š” ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์“ฐ๋Š” ๊ฒฝ์šฐ๋งŒ !
interface HomeProps {
  nowPlayingMovies: IMovieInfo[];
  topRatedMovies: IMovieInfo[];
  popularMovies: IMovieInfo[];
  upComingMovies: IMovieInfo[];
}

export default function Home({
  nowPlayingMovies,
  topRatedMovies,
  popularMovies,
  upComingMovies,
}: HomeProps) {
  return (
    <>
      <div>
        <FirstMovie movies={upComingMovies} />
        <TextInfo name={"Previews"} isPreview={true} />
        <MovieList movies={upComingMovies} isPreview={true} />
        <TextInfo name={"Now Playing"} isPreview={false} />
        <MovieList movies={nowPlayingMovies} isPreview={false} />
        <TextInfo name={"Top Rated"} isPreview={false} />
        <MovieList movies={topRatedMovies} isPreview={false} />
        <TextInfo name={"Popular"} isPreview={false} />
        <MovieList movies={popularMovies} isPreview={false} />
      </div>
    </>
  );
}

...
// MovieList.tsx
import Link from "next/link";
import { IMovieInfo } from "../../interface";

interface MovieListProps {
  movies: IMovieInfo[];
  isPreview: boolean;
}

export default function MovieList({ movies, isPreview }: MovieListProps) {
  return (
    <div className="container">
      {movies.map((m: any) => (
        <Link
          href={{
            pathname: `/movies/${m.id}`,
            query: {
              title: m.original_title,
              poster: m.poster_path,
              overview: m.overview,
            },
          }}
          as={`/movies/${m.id}`}
          key={m.id}
        >
          <img
            className={isPreview ? "isCircle" : ""}
            src={"http://image.tmdb.org/t/p/w500" + m.backdrop_path}
            alt={m.title}
          />
        </Link>

...ํ›„๋žต

<Link/> ๋Š” Next.js ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.


<Link href= `/movies/${m.id}`></Link>

๊ธฐ๋ณธํ˜•์€ ์ด๋ ‡๊ฒŒ ์ด๋™ํ•  ๊ฒฝ๋กœ๋ฅผ href ์— ๋„ฃ์–ด์ฃผ๋Š”๊ฑด๋ฐ,


// MovieList.tsx

<Link
  href={{
    pathname: `/movies/${m.id}`,
    query: {
      title: m.original_title,
      poster: m.poster_path,
      overview: m.overview,
    },
  }}
  as={`/movies/${m.id}`}
  key={m.id}
>

์ด๋ ‡๊ฒŒ ์ด๋™ํ•˜๋Š” ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ (์—ฌ๊ธฐ์„œ๋Š” /movies/[...movieData].tsx)์— query ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํ•จ๊ป˜ ์ „๋‹ฌ ๊ฐ€๋Šฅํ•˜๋‹ค.


// movies/[...MovieData].tsx

import { useRouter } from "next/router";

export default function MovieDetail() {
  const router = useRouter();
  const { title, poster, overview } = router.query;

  return (
    <div>
      <img src={`https://image.tmdb.org/t/p/w500/${poster}`} />
      <button>โ–ถ๏ธ Play</button>
      <h2>{title}</h2>
      <div>{overview}</div>
    </div>
  );
}

useRouter() ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๊ณ  ์‚ฌ์šฉํ•ด์ฃผ๋Š”๊ฒŒ ๊ฐ€๋Šฅํ•˜๋‹ค !


๋งŒ์•ฝ query์ •๋ณด๋ฅผ ์•ˆ๋ณด๋‚ด์ค€๋‹ค๋ฉด ๊ฒฝ๋กœ์˜ m.id ๋ฅผ ์ด์šฉํ•˜์—ฌ api์—์„œ ์˜ํ™” ์ •๋ณด๋ฅผ ํ•œ๋ฒˆ ๋” ์š”์ฒญํ•ด์•ผํ•œ๋‹ค (์„œ๋ฒ„์—ฐ๊ฒฐ์„ ํ•œ๋ฒˆ ๋”... ใ… ใ… )

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ homeํ™”๋ฉด์—์„œ ํ•œ๋ฒˆ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•ด์„œ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋“ค์„ ํŽ˜์ด์ง€์— query ๊ฐ์ฒด๋กœ ์ „๋‹ฌํ•ด์ค€๋‹ค๋ฉด ์ถ”๊ฐ€์ ์ธ ์„œ๋ฒ„ํ†ต์‹  ์—†์ด ๋ฐ”๋กœ ํ™”๋ฉด์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค !


3. <style jsx></style>

// TextInfo.tsx

import styled, { css } from 'styled-components';
import {ITextInfo} from '../../interfaces/interface'

export default function TextInfo({name, isPreview}:ITextInfo){
    return(
        <Container isPreview = {isPreview}>
            {name}
        </Container>
    )
}

interface Props {
    isPreview: boolean;
}

const Container = styled.div<Props>`
    font-weight: 700;
    margin-bottom: 14px;
    font-size: ${props => props.isPreview ? "26.75px" : "20.92px"}
`

ํ‰์†Œ์ฒ˜๋Ÿผ styled-component๋กœ ์Šคํƒ€์ผ์„ ์ง ๋‹ค๋ฉด ์ด๋ ‡๊ฒŒ ๋˜๋Š” ํŒŒ์ผ์ด ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ์•„๊นŒ ์–ธ๊ธ‰ํ•œ๊ฒƒ์ฒ˜๋Ÿผ Next์—์„œ ์Šคํƒ€์ผ๋“œ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•˜๋ฉด ์ƒˆ๋กœ๊ณ ์นจํ–ˆ์„ ๋•Œ ์Šคํƒ€์ผ์ด ์ ์šฉ๋˜์ง€ ์•Š๋Š” ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์— ์ƒˆ๋กœ์šด ๋ฐฉ์‹์œผ๋กœ ์Šคํƒ€์ผ์„ ์ ์šฉํ•ด์ค˜์•ผํ•œ๋‹ค.

๋ฐ”๋กœ๋ฐ”๋กœ style jsx !!

<style jsx>{` `}</style>

์ปดํฌ๋„ŒํŠธ ์•ˆ์— ๋ณธ ์ฝ”๋“œ๋ฅผ ๋„ฃ์œผ๋ฉด ์ € ๋ฐฑํ‹ฑ ์‚ฌ์ด์— css ๋ฅผ ์ž‘์„ฑํ•ด์„œ ์Šคํƒ€์ผ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด ๊ฒฝ์šฐ JS ํŒŒ์ผ์„ ๋กœ๋“œํ•˜์ง€ ์•Š๋”๋ผ๋„ pre-rendering ๊ณผ์ •์—์„œ html ์„ ๊ตฌ์„ฑํ• ๋•Œ ์Šคํƒ€์ผ ์ ์šฉ์ด ํ•จ๊ป˜ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์œ„ ์˜ค๋ฅ˜๋„ ์ƒ๊ธฐ์ง€ ์•Š๋Š”๋‹ค


// TextInfo.tsx
import { ITextInfo } from "../../interface";

export default function TextInfo({ name, isPreview }: ITextInfo) {
  return (
    <>
      <div className={isPreview ? "isPreview" : ""}>{name}</div>
      <style jsx>{`
        div {
          font-weight: 700;
          margin-bottom: 14px;
          font-size: 20.92px;
        }
        .isPreview {
          font-size: 26.75px;
        }
      `}</style>
    </>
  );
}

styled-component๋ฅผ ์‚ฌ์šฉํ–ˆ๋˜ ๋ชจ๋“  ํŒŒ์ผ๋“ค์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด style ํƒœ๊ทธ๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ์—ˆ๋‹ค ํ•˜ ํ•˜ ! ๋…ธ๊ฐ€๋‹ค !




๐Ÿ“‚ Foldering

NextJs ์˜ ํŠน์„ฑ์ƒ ํด๋”๊ตฌ์กฐ๊ฐ€ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค !

โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ public
โ”‚   โ”œโ”€โ”€ favicon.ico
โ”‚   โ”œโ”€โ”€ img --- ๊ฐ ์ปดํฌ๋„ŒํŠธ์— ํ•„์š”ํ•œ ์ด๋ฏธ์ง€ ์—์…‹๋“ค
โ”‚   โ”‚   โ”œโ”€โ”€ Footer
โ”‚   โ”‚   โ””โ”€โ”€ Header
โ”‚   โ””โ”€โ”€ vercel.svg
โ”œโ”€โ”€ src
โ”‚   โ”œโ”€โ”€ api.tsx --- api ๊ฒฝ๋กœ ๋ชจ์•„๋‘” ํŒŒ์ผ
โ”‚   โ”œโ”€โ”€ assets
โ”‚   โ”‚   โ”œโ”€โ”€ FooterInfo.json --- ํ‘ธํ„ฐ ๋ฐ์ดํ„ฐ ๋”ฐ๋กœ ๋ฝ‘์•„๋†“์€ ํŒŒ์ผ
โ”‚   โ”‚   โ””โ”€โ”€ config.tsx --- API_KEY ๋„ฃ์–ด๋‘” ํŒŒ์ผ
โ”‚   โ”œโ”€โ”€ components
โ”‚   โ”‚   โ”œโ”€โ”€ common --- ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Footer.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ FooterItem.tsx
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ Header.tsx
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ Layout.tsx --- ๋ ˆ์ด์•„์›ƒ
โ”‚   โ”‚   โ””โ”€โ”€ home --- ํŠน์ • ํŽ˜์ด์ง€์—์„œ ์“ฐ์ด๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค
โ”‚   โ”‚       โ”œโ”€โ”€ FirstMovie.tsx
โ”‚   โ”‚       โ”œโ”€โ”€ MovieList.tsx
โ”‚   โ”‚       โ””โ”€โ”€ TextInfo.tsx
โ”‚   โ”œโ”€โ”€ interface.tsx --- ๊ณตํ†ต์ €๊ธ๋กœ ์“ฐ์ด๋Š” ts interface
โ”‚   โ””โ”€โ”€ pages --- ๊ฐ ํŽ˜์ด์ง€ (ํŒŒ์ผ๋ช…์€ ๊ฒฝ๋กœ์ด๋ฆ„๊ณผ ๋™์ผ)
โ”‚       โ”œโ”€โ”€ _app.tsx --- ๊ฐ€์žฅ ๋จผ์ € ๋ Œ๋”๋ง ๋˜๋Š” ๊ธฐ๋ณธ ํŒŒ์ผ
โ”‚       โ”œโ”€โ”€ home.tsx --- ๊ฒฝ๋กœ : /home
โ”‚       โ”œโ”€โ”€ index.tsx --- ๊ฒฝ๋กœ : /
โ”‚       โ”œโ”€โ”€ movies
โ”‚       โ”‚   โ””โ”€โ”€ [...movieData].tsx --- ๊ฒฝ๋กœ :/movies/movie_id
โ”‚       โ””โ”€โ”€ search.tsx --- ๊ฒฝ๋กœ : /search
โ”œโ”€โ”€ styles
โ”‚   โ””โ”€โ”€ globals.css --- ์ „์—ญ ์Šคํƒ€์ผ
โ””โ”€โ”€ tsconfig.json



๐Ÿ”ฎ Later

  • search api.. ๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด์•ผํ•œ๋‹ค..
  • ์•„์ง ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜์ง€ ์•Š์€ ํŽ˜์ด์ง€๋“ค.. ์Šคํƒ€์ผ ์ ์šฉํ•˜๊ธฐ..
    • MovieDetail.tsx
    • [...MovieData].tsx

profile
๊น€์„ ๋‹ฌ ๊ฐœ๋ฐœ๋ธ”๋กœ๊ทธ

2๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2022๋…„ 12์›” 1์ผ

ํ˜น์‹œ ๊นƒํ—ˆ๋ธŒ ์ฃผ์†Œ๋ฅผ ์•Œ์ˆ˜์žˆ์„๊นŒ์š”? ๊ณต๋ถ€ํ•˜๊ณ ์‹ถ์€๋ฐ..

1๊ฐœ์˜ ๋‹ต๊ธ€