본문 바로가기

영화/React, NextJS, Typescript, Vercel

18. Deployment(배포)

1. Vercel 계정 생성

 

Vercel: Build and deploy the best web experiences with the Frontend Cloud – Vercel

Vercel's Frontend Cloud gives developers the frameworks, workflows, and infrastructure to build a faster, more personalized web.

vercel.com

 

 

 

 

 

2. CSS Modules 추가

Next Js는 CSS 적용할때 어떠한 Configuration(구성)을 하지 않고 CSS파일을 import만 하면 된다.

모든 페이지들은 layout 파일이 근간이기 때문에 모두 같은 CSS 파일을 공유한다.

 

 

styles > global.css

html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
  display: block;
}

body{
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    background-color: black;
    color: white;
    font-size: 18px;
}

a{
    color : inherit;
    text-decoration: none;
}

a:hover{
    text-decoration: underline;
}

 

 

app > layout.tsx

import "../styles/global.css";
import { Metadata } from "next";
import Navigation from "../components/navigation"

export const metadata : Metadata = {
  title: {
    template : "%s | Next Movies",
    default : "Loading..."
  }
};

export default function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
   
        <Navigation />

        {children}
      </body>
    </html>
  )
}

 

 

styles > navigation.module.css

.nav{
    background-color: red;
    padding: 50px 100px;
}

.list{
    display: flex;
}

 

 

components > navigation.tsx

"use client"

import styles from "../styles/navigation.module.css"

import Link from "next/link";
import { usePathname } from "next/navigation";

export default function Navigation(){
    const path = usePathname();
    return(
        <nav className={styles.nav}>
            <ul className={styles.list}>
                <li>
                    <Link href = "/"> 
                            Home
                    </Link>
                    {path === "/" ? "🔥" : ""}
                </li>
                <li>
                    <Link href = "/about-us">
                        About Us
                    </Link>   
                    {path === "/about-us" ? "🔥" : ""}
                </li>
            </ul>
        </nav>
    )
}

 

 

learn-nextjs14.z01
19.53MB
learn-nextjs14.z02
19.53MB
learn-nextjs14.z03
19.53MB
learn-nextjs14.z04
19.53MB
learn-nextjs14.z05
19.53MB
learn-nextjs14.z06
19.53MB
learn-nextjs14.z07
19.53MB
learn-nextjs14.z08
19.53MB
learn-nextjs14.zip
12.76MB

 

 

 

 

 

 

3. Movie Styles 구성

 

app > (home) > page.tsx

import styles from "/styles/home.module.css";
import Movie from "../../components/movie";

export const metadata = {
  title : "Home"
}

export const API_URL = "https://nomad-movies.nomadcoders.workers.dev/movies";

async function getMovies(){
  const response = await fetch(API_URL);
  const json = await response.json();
  return json;
};

export default async function HomePage(){

    const movies = await getMovies();

    return (
      <div className={styles.container}>
        {movies.map((movie) =>(
          <Movie key={movie.id} 
            id = {movie.id} 
            poster_path = {movie.poster_path}
            title = {movie.title}
          />
        ))}
      </div>

    );
}

 

 

component > movie.tsx

"use client";

import { useRouter } from "next/navigation";
import styles from "/styles/movie.module.css";
import Link from "next/link";

interface IMovieProps{
    title : string;
    id : string;
    poster_path : string;
}


export default function Movie({title, id, poster_path} : IMovieProps){

    const router = useRouter();
    const onClick = () => {
        router.push(`/movies/${id}`);
    }; 

    return (

        
        <div className={styles.movie}>

            <img src = {poster_path} alt={title} onClick={onClick}/>
            <Link href = {`/movies/${id}`}>{title}</Link>

        </div>
        
        
    )

}

 

 

styles > global.css

html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
  display: block;
}
body {
  line-height: 1;
}
ol,
ul {
  list-style: none;
}
blockquote,
q {
  quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
  content: "";
  content: none;
}
table {
  border-collapse: collapse;
  border-spacing: 0;
}

body {
  padding-top: 150px;
  font-family:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    Oxygen,
    Ubuntu,
    Cantarell,
    "Open Sans",
    "Helvetica Neue",
    sans-serif;
  background-color: black;
  color: white;
  font-size: 18px;
}

a {
  color: inherit;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

 

 

styles > home.module.css

.container {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 25px;
  max-width: 90%;
  width: 100%;
  margin: 0 auto;
}

 

 

styles > movie.module.css

.movie {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: 20px;
  cursor: pointer;
  place-items: center;
}

.movie img {
  max-width: 100%;
  min-height: 100%;
  border-radius: 10px;
  transition: opacity 0.3s ease-in-out;
}

.movie img {
  opacity: 0.7;
}

.movie img:hover {
  opacity: 1;
}

.movie a {
  text-align: center;
}

 

 

styles > navigation.module.css

.nav {
  background-color: #2d2d2d;
  position: fixed;
  width: 30%;
  margin: 0 auto;
  top: 20px;
  border-radius: 50px;
  padding: 20px 0px;
  left: 50%;
  z-index: 10;
  transform: translateX(-50%);
}

.nav ul {
  display: flex;
  justify-content: center;
  gap: 50px;
}

.nav ul li {
  list-style: none;
  transform: none;
  transition: all 0.1s ease-in-out;
}

.nav ul li:hover {
  transform: scale(1.05);
}

 

 

learn-nextjs14.z01
19.53MB
learn-nextjs14.z02
19.53MB
learn-nextjs14.z03
19.53MB
learn-nextjs14.z04
19.53MB
learn-nextjs14.z05
19.53MB
learn-nextjs14.z06
19.53MB
learn-nextjs14.z07
19.53MB
learn-nextjs14.z08
19.53MB
learn-nextjs14.z09
19.53MB
learn-nextjs14.zip
1.61MB

 

 

 

 

 

4. Movie Trailers 구성

 

 

 

styles > movie-info.module.css

.container {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 50px;
  width: 80%;
  margin: 0 auto;
}

.poster {
  border-radius: 20px;
  max-width: 70%;
  place-self: center;
}

.title {
  color: white;
  font-size: 36px;
  font-weight: 600;
}

.info {
  display: flex;
  flex-direction: column;
  margin-top: 20px;
  gap: 20px;
}

 

 

styles > movie-videos.module.css

.container {
  width: 80%;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
  margin-top: 100px;
  padding-bottom: 100px;
}

.container iframe {
  border-radius: 10px;
  opacity: 0.8;
  transition: opacity 0.2s ease-in-out;
}

.container iframe:hover {
  opacity: 1;
}

 

 

components > movie-info.tsx

import potato from "/styles/movie-info.module.css";
import { API_URL } from "../app/(home)/page";

async function getMovie(id:string){

    const response = await fetch(`${API_URL}/${id}`);
    return response.json();
};

export default async function Movieinfo({id} : {id : string}){
    
    const movie = await getMovie(id);

    return (
        <div className={potato.container}>
            <img src = {movie.poster_path} className={potato.poster} alt={movie.title} />
                <div className={potato.info}>
                    <h1 className={potato.title}>{movie.title}</h1>
                    <h3>⭐️{movie.vote_average.toFixed(1)}</h3>
                    <p>{movie.overview}</p>
                    <a href = {movie.homepage} target={"_blank"}>Homepage &rarr; </a>
                </div>
        </div>
    )
};

 

 

components > movie-videos.tsx

import styles from "/styles/movie-videos.module.css"
import { API_URL } from "../app/(home)/page";

async function getVideos(id : string){
    const response = await fetch(`${API_URL}/${id}/videos`);
    return response.json();
};

export default async function MovieVideos({id} : {id : string}){
    
    const videos = await getVideos(id);

    return (
        <div className={styles.container}>
            {videos.map((video) => (
                <iframe 
                    key={video.id} 
                    src={`https://youtube.com/embed/${video.key}`} 
                    title={video.name}
                    allow="accelerometer; autuplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
                    allowFullScreen
                > 

                </iframe>
            ))}
        </div>
    )
};

 

 

learn-nextjs14.z01
19.53MB
learn-nextjs14.z02
19.53MB
learn-nextjs14.z03
19.53MB
learn-nextjs14.z04
19.53MB
learn-nextjs14.z05
19.53MB
learn-nextjs14.z06
19.53MB
learn-nextjs14.z07
19.53MB
learn-nextjs14.z08
19.53MB
learn-nextjs14.z09
19.53MB
learn-nextjs14.zip
1.60MB

 

 

 

 

 

 

5. Dynamic Metadata 적용

 

 

components > movie-info.tsx

import potato from "/styles/movie-info.module.css";
import { API_URL } from "../app/(home)/page";

export async function getMovie(id:string){

    const response = await fetch(`${API_URL}/${id}`);
    return response.json();
};

export default async function Movieinfo({id} : {id : string}){
    
    const movie = await getMovie(id);

    return (
        <div className={potato.container}>
            <img src = {movie.poster_path} className={potato.poster} alt={movie.title} />
                <div className={potato.info}>
                    <h1 className={potato.title}>{movie.title}</h1>
                    <h3>⭐️{movie.vote_average.toFixed(1)}</h3>
                    <p>{movie.overview}</p>
                    <a href = {movie.homepage} target={"_blank"}>Homepage &rarr; </a>
                </div>
        </div>
    )
};

 

 

app > (movie) > movies > [id] > page.tsx

import { Suspense } from "react";
import Movieinfo, { getMovie } from "../../../../components/movie-info";
import MovieVideos from "../../../../components/movie-videos";

interface IParams {
    params : {id : string};
}

export async function generateMetadata({params : {id}} : IParams){
    const movie = await getMovie(id);
    return{
        title : movie.title
    }
}

export default async function MovieDetail({params : {id}} : IParams){

    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>
    );

};

 

 

 

 

 

 

 

6. Github 커밋

 

6-1) Git 설치
https://git-scm.com/downloads


6-2) 최초 세팅
6-2-1) git init .                                                          
       ** git init 시 기본 브랜치가 자동으로 생성되는데,  
           커밋이 없으면 브랜치가 실제로 생성된 것이 아니라고 본다.
           (커밋이 있어야 브랜치가 본격적으로 존재)
6-2-2) GitHub에서 repository를 생성한다.
6-2-3) git remote add origin 주소
  


6-3) 변경된 파일(또는 새 파일) 모두 스테이징하기
git add .


6-4) 변경사항 커밋 및 메시지 작성
git commit -m 내용
    ex) git commit -m "Fix remote and prepare deploy"

 


6-5) 커밋 확인
git log


6-6) 현재 브랜치 확인
git branch


6-7) 원격 저장소에 푸시
git push -u origin master

 

 

 

 

7. Vercel 사이트에서 배포

 

 

 

 

 

 

 

 

첫번째, Project Name이 디폴트로 입력되어 있는데 원하는 이름이 있으면 수정한다.

두번째, Root Directory는 수정하면 안된다.

세번째, Build and Output Settings를 보면 npm runn build가 자동으로 실행되는걸 확인할 수 있다.

네번째, 그리고 나중에 자동으로 npm run start도 실행된다.

다섯번째, Deploy 버튼을 클릭한다.

 

 

 

 

 

 

 

 

 

 

19. Deployment(배포) 완료 소스 압축 (1)

 

bonsimony.tistory.com

 

 

20. Deployment(배포) 완료 소스 압축 (2)

 

bonsimony.tistory.com