npx create-next-app@latest
npm run dev
/pagesjsx일 필요 없음. React 를 import 할 필요 없음.html 을 받게됨. 이후에 브라우저가 자바스크립트를 로드한 후 실행해서 화면을 그리게 됨.만약 브라우저에서 자바스크립트를 활성화 하지 않았을 때 noscript 가 있었다면 아래만 보여지게 됨
<noscript>자바스크립트가 활성화 되지 않았습니다.</noscript>
react js 를 실행해서 html 을 완성한 후 보내기 때문에 빈화면이 아닌 채워진 화면을 보게됨.react js 가 클라이언트에서 동작하는 것을 말함LinkNextJS 에서 라우팅할 때는 Link 를 사용해야 함. 그래야 CSR를 할 수 있음.
// components/NavBar.js
import Link from "next/link";
export default function NavBar() {
return (
<nav>
<Link href="/">
<a>HOME</a>
</Link>
<Link href="/about">
<a>ABOUT</a>
</Link>
</nav>
);
}
a 를 Link 로 감싸주는 이유는 style 이나 class 처럼 다른 어트리뷰트를 사용할 수 없기 때문임.
Link 는 href 만을 위한 컴포넌트임. 그 외의 것은 a 에게 전달하면 됨.href 에 객체를 전달하면 pathname 과 query를 지정할 수 있음.
<Link
href={{
pathname: `/movies/${movie.id}`,
query: {
title: movie.title,
},
}}
as={`/movies/${movie.id}`}
>
location 정보를 얻을 수 있음.pathname 으로 현재 url을 알 수 잇음import Link from "next/link";
import { useRouter } from "next/router";
import styles from "./NavBar.module.css";
export default function NavBar() {
const router = useRouter();
console.log(router);
return (
<nav className="nav">
<Link href="/">
<a style={{ color: router.pathname === "/" ? "red" : "black" }}>HOME</a>
</Link>
<Link href="/about">
<a style={{ color: router.pathname === "/about" ? "red" : "black" }}>
ABOUT
</a>
</Link>
</nav>
);
}
console 결과

렌더링 결과

.nav {
display: flex;
justify-content: space-between;
background-color: tomato;
}
styles.nav 로 css 파일에 있는 클래스 이름을 가져다가 사용하면 됨import Link from "next/link";
import { useRouter } from "next/router";
import styles from "./NavBar.module.css";
export default function NavBar() {
const router = useRouter();
console.log(router);
return (
<nav className={styles.nav}>
<Link href="/">
<a>HOME</a>
</Link>
<Link href="/about">
<a>ABOUT</a>
</Link>
</nav>
className 에 여러개의 클래스를 넣고싶다면, 띄어쓰기를 포함한 문자열로 작성해야함. <a
className={`${styles.link} ${
router.pathname === "/" ? styles.active : ""
}`}
>
HOME
</a>
// ....
<a
className={[
styles.link,
router.pathname === "/about" ? styles.active : "",
].join("")}
>
ABOUT
</a><style jsx>color: ${props.color}active 처럼 클래스를 사용하더라도 네이밍 스페이스가 한정됨
import Link from "next/link";
import { useRouter } from "next/router";
export default function NavBar() {
const router = useRouter();
console.log(router);
return (
<nav>
<Link href="/">
<a>HOME</a>
</Link>
<Link href="/about">
<a>ABOUT</a>
</Link>
<style jsx>{`
nav {
background-color: tomato;
}
a {
text-decoration: none;
}
`}</style>
</nav>
);
}
_app.js)NavBar 이나 글로벌 스타일 처럼 모든 페이지에서 공통적으로 사용하는 것들을 _app.js 파일에서 한번에 적용할 수 있음_app.js 를 불러와서 실행할 것임{ Component, pageProps })로 보내준 다음 합쳐진 것을 그려줌<style jsx global> 로 글로벌 스타일을 적용할 수 있음.// pages/_app.js
import NavBar f플
globals.css 처럼 css 파일은 index.js 에서 import 할 수 없음. 하지만 _app.js 에서는 가능하다.// pages/_app.js
import "../styles/globals.css";
_app.js 에 넣을 것이 많기 때문에, _app 의 크기가 커지는 것을 보통 지양함Layout 컴포넌트를 만들어서 _app.js 에서 사용하도록 한다. (Layout 자체를 app에서 구현 안한다는 말)// components/Layout
import NavBar from "./NavBar";
export default function Layout({ children }) {
return (
<>
<NavBar />
{children}
</>
);
}
// pages/_app
import Layout from "../components/Layout";
import "../styles/globals.css";
export default function App({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
Headreact-helmet 으로 구현했던 head 를 간단히 Head 컴포넌트를 사용해서 구현 가능// components/Seo.js
import Head from "next/head";
import { useRouter } from "next/router";
import { TITLE } from "./const";
export default function Seo() {
const { pathname } = useRouter();
return (
<Head>
<title>{TITLE[pathname]} | Next</title>
</Head>
);
}
export const TITLE = {
"/": "Home",
"/about": "About",
};
→ 근데 개발을 진행하다보니, TITLE 객체를 계속 추가해야하고, 영화 title 과같이 완전 램덤인 값들을 처리할려고 하다보니 404 페이지 띄우기 애매해지는 문제가 발생함. 그냥 그때그때 SEO 컴포넌트를 추가하는게 나은거 같음.
/ 경로가 되어서, /photo.png 이렇게 경로를 지정하면 된다.
Imagehttps://velog.io/@pyo-sh/React-NextJS-에서-이미지-import-하기
785c2b2480182aa560d1a17dd4391c02
useEffect(() => {
(async () => {
const { results } = (
await fetch(`${BASE_URL}/3/movie/popular?api_key=${API_KEY}`)
).json();
setMovies(results);
console.log(results);
})();
}, []);
API key 가 쉽게 노출된다.
API키를 숨기지 않는 방법
유저를 리다이렉트하며 url이 변경된다.
next.config.jsredirects 함수는 비동기적으로 동작하므로 async를 작성한다.
redirecting에 대한 객체를 배열에 담아 반환한다.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
async redirects() {
return [
{
source: "/old-blog/:path*", // 사용자기 여기로 접근하면
destination: "/new-blog/:path*", // 여기로 redirect 시켜준다.
permanent: false,
},
];
},
};
module.exports = nextConfig;
API를 숨기는 방법
유저를 리다이렉트 하지만 url이 변경되지 않음.

/** @type {import('next').NextConfig} */
const API_KEY = "785c2b2480182aa560d1a17dd4391c02"; // process.env.API_KEY
const nextConfig = {
reactStrictMode: true,
async rewrites() {
return [
{
source: "/api/movies",
destination: `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`,
},
];
},
};
module.exports = nextConfig;
.env 에 API_KEY를 저장하면 git 에 API 키가 올라가는 것을 막을 수 있음. (.gitignore에 .env 를 추가해야함)// .env
API_KEY=785c2b2480182aa560d1a17dd4391c02getServerSidePropsimport { useEffect, useState } from "react";
import Seo from "../components/Seo";
// SSR - getServerSideProps
export async function getServerSideProps() {
const { results } = await (
await fetch(`http://localhost:3000/api/movies`)
).json();
return {
props: {
results,
},
};
}
export default function Home({ results }) {
return (
<div className="container">
<Seo title="Home" />
{!results && <h4>Loading...</h4>}
{results?.map((movie) => (
<div className="movie" key={movie.id}>
<img src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} />
<h4>{movie.original_title}</h4>
</div>
))}
</div>
);
}
props 로 사용할 수 있음!
/movies/all
/movies/:id대괄호를 사용해서 다이나믹 라우팅을 해줄 수 있음.


useRouter 를 통해서 query 의 id를 찾을 수 있음.
LinkLink를 사용해서 라우팅
{results?.map((movie) => (
<div className="movie" key={movie.id}>
<img src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} />
<Link href={`/movies/${movie.id}`}>
<a>
<h4>{movie.original_title}</h4>
</a>
</Link>
</div>
))}
router.push onclick 되었을 때 url 를 변경하도록 push 함수를 호출할 수 있음
const router = useRouter();
const onClick = (id) => {
router.push(`/movies/${id}`);
};
// ...
<div onClick={() => onClick(movie.id)} className="movie" key={movie.id}>
// ...
query 를 사용해 end point를 직접 작성해줄 수 있음 const router = useRouter();
const onClick = (id) => {
router.push({
pathname: `/movies/${id}`,
query: {
id,
title: "hello",
},
});
};

pathname?queryKey=queryvalue
query 부분을 as 를 사용해서 숨길 수 있음. const router = useRouter();
const onClick = (id) => {
router.push(
{
pathname: `/movies/${id}`,
query: {
id,
title: "hello",
},
},
`/movies/${id}`
);
};
url 에는 query가 없음
useRouter 를 사용하면 query를 꺼내올 수 있다! 이것도 Link 에서 동일하게 사용 가능하다.
/api/movies/:id로 우회하도록 추가/** @type {import('next').NextConfig} */
const API_KEY = process.env.API_KEY;
const nextConfig = {
reactStrictMode: true,
async rewrites() {
return [
{
source: "/api/movies",
destination: `https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`,
},
{
source: "/api/movies/:id",
destination: `https://api.themoviedb.org/3/movie/:id?api_key=${API_KEY}`,
},
];
},
};
module.exports = nextConfig;
/api/movies/:id 로 요청을 보내면 정상적으로 백엔드 api에서 받은 응답을 확인할 수 있음
[...][...] 를 통해서 movies 아래 모든 end point 들을 여기로 라우팅 할 수 있음
Link 의 href 를 아래와 같이 수정한 다음<Link href={`/movies/${movie.original_title}/${movie.id}`}>useRouter 를 사용해 params 배열을 배열 디스트럭처링 하면 사용할 수 있다.import { useRouter } from "next/router";
export default function Detail() {
const router = useRouter();
const [title, id] = router.query.params;
return (
<div>
<h2>{title || "loading..."}</h2>
</div>
);
}
// url = "/movies/title/id"

query 의 params 는 배열임
params 가 배열이 아니므로, 시크릿 모드로 캐시가 모두 사라진 경우에 query가 담긴 url 로 접근하려고 하면 오류가 발생한다.따라서 [] 를 아래처럼 추가해주어야 에러가 나지 않음
하지만 이건 CSR 만 해준 것임
const router = useRouter();
const [title, id] = router.query.params || [];
getServerSidePropsctx 에는 query 와 params 정보들이 담겨져 있음import { useRouter } from "next/router";
export default function Detail() {
const router = useRouter();
const [title, id] = router.query.params || [];
return (
<div>
<h2>{title || "loading..."}</h2>
</div>
);
}
// url = "/movies/title/id"
export async function getServerSideProps(ctx) {
console.log(ctx); // query, params 정보들
return {
props: {},
};
}

export default function Detail({ params }) {
const [title, id] = params || [];
return (
<div>
<h2>{title || "loading..."}</h2>
</div>
);
}
// url = "/movies/title/id"
export async function getServerSideProps({ params: { params } }) {
return {
props: { params },
};
}

import Head from "next/head";
import { useRouter } from "next/router";
import { TITLE } from "./const";
export default function Seo() {
const { pathname, query } = useRouter();
return (
<Head>
<title>
{TITLE[pathname] ? TITLE[pathname] : query.params[0]} | Next
</title>
</Head>
);
}

폴더구조는 아래와 같다.

export default function NotFound() {
return "404 페이지 입니다";
}