[NextJS] Routing

โœจ ๊ฐ•์€๋น„ยท2022๋…„ 6์›” 10์ผ
0

NextJS

๋ชฉ๋ก ๋ณด๊ธฐ
1/10
post-thumbnail

๐Ÿงฉ Routing์ด๋ž€?

  • Routing์€ ๋ธŒ๋ผ์šฐ์ € ๊ฒฝ๋กœ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํ™”๋ฉด(ํŽ˜์ด์ง€)์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.
  • React์—์„œ๋Š” routing์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ๋กœ react-router-dom ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • ๋ฐ˜๋ฉด, Next์—์„œ๋Š” ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค.

NextJS ๊ณต์‹๋ฌธ์„œ ๐Ÿ‘


๐Ÿงฉ NextJS์˜ Routing

ํŒŒ์ผ ์‹œ์Šคํ…œ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ •์  ๋ผ์šฐํŒ…๊ณผ ๋™์  ๋ผ์šฐํŒ…์„ ์ œ๊ณตํ•œ๋‹ค.

๐Ÿ“ pages ํด๋”

  • NextJS ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด pages๋ผ๋Š” ํด๋”๊ฐ€ ๋งŒ๋“ค์–ด์ง„๋‹ค.
  • pages ํด๋” ์•ˆ์— ์žˆ๋Š” ํŒŒ์ผ๋ช…์— ๋”ฐ๋ผ route๊ฐ€ ๊ฒฐ์ •๋œ๋‹ค. => ๐Ÿš— automatic routing
  • ์ด๋•Œ ํŒŒ์ผ์— ์ž‘์„ฑ๋œ ์ปดํฌ๋„ŒํŠธ๋ช…์€ ์ค‘์š”ํ•˜์ง€ ์•Š๊ณ , ์ปดํฌ๋„ŒํŠธ๋ฅผ export default ํ•ด์•ผ ํ•œ๋‹ค.
    • pages/about.js ํŒŒ์ผ ์ƒ์„ฑ -> localhost:3000/about
    • pages/index.js -> localhost:3000/index (x) localhost:3000 (o)

Link ์ปดํฌ๋„ŒํŠธ์— href ์†์„ฑ์„ ๋„ฃ๊ณ , ๋‚˜๋จธ์ง€ ํƒœ๊ทธ ์†์„ฑ๋“ค(ex. className)์€ anchor ํƒœ๊ทธ์— ๋„ฃ๋Š”๋‹ค.


๐Ÿช useRouter hook

const router = useRouter();
  • Routing ์ •๋ณด๋ฅผ ๋‹ด์€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • router.push(url, as, options) : next/link๋กœ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•œ๋‹ค.
    • url : UrlObject | String, ํƒ์ƒ‰ํ•  URL
    • as : UrlObject | String, ๋ธŒ๋ผ์šฐ์ € URL ํ‘œ์‹œ์ค„์— ํ‘œ์‹œ๋  ๊ฒฝ๋กœ

๐Ÿ’ป Link and useRouter Example

import Link from "next/link";
import { useRouter } from "next/router";
import styled from "styled-components";

const Anchor = styled.a<{ router: string; pathName: string }>`
  color: ${(props) => (props.pathName === props.router ? "red" : "black")};
  cursor: pointer;
`;

const NavBar = () => {
  const router = useRouter();

  return (
    <nav>
      <Link href="/">
        <Anchor pathName="/" router={router.pathname}>
          Home
        </Anchor>
      </Link>
      <Link href="/about">
        <Anchor pathName="/about" router={router.pathname}>
          About
        </Anchor>
      </Link>
    </nav>
  );
};

export default NavBar;

๐Ÿ’ฅ Dynamic Routes

  • pages ํด๋” ์•ˆ์— ์žˆ๋Š” ํŒŒ์ผ๋ช…์— ๋”ฐ๋ผ route๊ฐ€ ๊ฒฐ์ •๋œ๋‹ค๊ณ  ์•ž์„œ ์„ค๋ช…ํ–ˆ๋‹ค.
  • ์ด๋•Œ ํŒŒ์ผ๋ช…์— ๋Œ€๊ด„ํ˜ธ๋ฅผ ๋„ฃ์–ด dynamic route๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

ex) pages/movies/[id].tsx -> http://localhost:3000/movies/:id

const router = useRouter();
const { id } = router.query;

๐Ÿ’ฅ Dynamic Routes Example

// components/Movie.tsx

import Link from "next/link";
import { useRouter } from "next/router";
import { IMovieProps } from "../lib/api/movies";
import styled from "styled-components";


// ... styled components ์ƒ๋žต 

const Movie = ({ movie }: { movie: IMovieProps }) => {
  const router = useRouter();
  const onClick = (id: number, title: string) => {
    router.push(
      {
        pathname: `/movies/${id}`,
        query: {
          title,
        },
      },
      `/movies/${id}` // URL masking
    );
  };
  return (
    <StyledMovie onClick={() => onClick(movie.id, movie.original_title)}>
      <Image src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} />
      <h4>{movie.original_title}</h4>
    </StyledMovie>
  );
};

export default Movie;
// pages/movies/[id].tsx
import { useRouter } from "next/router";
import CustomHead from "../../components/CustomHead";

const Detail = () => {
  const router = useRouter();
  const { id, title } = router.query;
  return (
    <main>
      <CustomHead title={title as string} />
      <h1>{id}</h1>
    </main>
  );
};

export default Detail;

๐ŸŒ Catch All Routes

๋Œ€๊ด„ํ˜ธ ์•ˆ์— ์„ธ ๊ฐœ์˜ ์ ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ํฌ์ฐฉํ•˜๋„๋ก dynamic routes๋ฅผ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

ex) pages/movies/[...params].tsx
	-> http://locahost:3000/movies/1
    -> http://locahost:3000/movies/asdf/2
    -> http://localhost:3000/movies/3/adf3/23a ๋ชจ๋‘ ์ผ์น˜

๐ŸŒ Catch-All-URL Example

// components/Movie.tsx
import Link from "next/link";
import { useRouter } from "next/router";
import { IMovieProps } from "../lib/api/movies";
import styled from "styled-components";

// ... styled components ์ƒ๋žต

const Movie = ({ movie }: { movie: IMovieProps }) => {
  const router = useRouter();
  const onClick = (id: number, title: string) => {
    router.push(`/movies/${title}/${id}`);
  };
  return (
    <StyledMovie onClick={() => onClick(movie.id, movie.original_title)}>
      <Image src={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`} />
      <h4>{movie.original_title}</h4>
    </StyledMovie>
  );
};

export default Movie;
// pages/movies/[...params].tsx

import { useRouter } from "next/router";
import CustomHead from "../../components/CustomHead";

type MovieDetailParams = [string, string] | [];

const Detail = () => {
  const router = useRouter();
  const [title, id] = router.query.params as MovieDetailParams; // ๋ฐฐ์—ด ๋น„๊ตฌ์กฐํ™” ํ• ๋‹น
  return (
    <main>
      <CustomHead title={title as string} />
      <h1>{title}</h1>
    </main>
  );
};

export default Detail;

๐Ÿšง 404 Page

pagesํด๋”์— 404.tsx ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด 404 page๋ฅผ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ๋‹ค.

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