npx create-next-app@latest
npm run dev
/pages
jsx
일 필요 없음. React
를 import 할 필요 없음.html
을 받게됨. 이후에 브라우저가 자바스크립트를 로드한 후 실행해서 화면을 그리게 됨.만약 브라우저에서 자바스크립트를 활성화 하지 않았을 때 noscript
가 있었다면 아래만 보여지게 됨
<noscript>자바스크립트가 활성화 되지 않았습니다.</noscript>
react js
를 실행해서 html
을 완성한 후 보내기 때문에 빈화면이 아닌 채워진 화면을 보게됨.react js
가 클라이언트에서 동작하는 것을 말함Link
NextJS 에서 라우팅할 때는 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>
);
}
Head
react-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
이렇게 경로를 지정하면 된다.Image
https://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.js
redirects
함수는 비동기적으로 동작하므로 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=785c2b2480182aa560d1a17dd4391c02
getServerSideProps
import { 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를 찾을 수 있음.Link
Link를 사용해서 라우팅
{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 || [];
getServerSideProps
ctx
에는 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 페이지 입니다";
}