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>
)
}
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);
}
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 → </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>
)
};
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 → </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
'영화 > React, NextJS, Typescript, Vercel' 카테고리의 다른 글
20. Deployment(배포) 완료 소스 압축 (2) (0) | 2025.05.16 |
---|---|
19. Deployment(배포) 완료 소스 압축 (1) (0) | 2025.05.16 |
17. Data Fetching(데이터 가져오기) - Error Handling(오류 처리) (0) | 2025.05.15 |
16. Data Fetching(데이터 가져오기) - Suspense(임시적 로딩상태) (0) | 2025.05.15 |
15. Data Fetching(데이터 가져오기) - 병렬 요청(Parell Request) (0) | 2025.05.14 |