Next.js로 웹사이트 만들기 (2) 라우팅

LeeKyungwon·2024년 5월 27일
0

프론트엔드 

목록 보기
41/56
post-custom-banner

파일 시스템 기반 라우팅

라우팅

어떤 주소에 어떤 페이지를 보여줄지 정하는 것

파일시스템 기반 라우팅

파일의 경로가 주소에 매칭되는 라우팅 방식, pages라는 이름의 폴더 안에 페이지 파일을 만들면 알아서 라우팅한다.

path 생성 구조

/pages
└ index.js -> my-domain.com/
└ about.js -> my-domain.com/about
└ /products
└ index.js -> my-domain.com/products
└ [id].js -> my-domain.com/products/1

next.js에서는 파일시스템 기반 라우팅 방식이 지원된다.

페이지 나누기

pages라는 폴더 이름은 next.js에서 정해주는 것이기 때문에 바꾸면 안 된다.
index.js : 홈페이지에 해당

페이지를 나누려면 페이지 파일에서 컴포넌트를 default로 export하면 된다.

export default function Search() {
  return <>Search Page</>;
}

다이나믹 라우팅

여러 주소를 하나의 페이지 컴포넌트에서 처리하는 것
파일 이름을 대괄호로 쓰면 안에 이름을 변수처럼 사용할 수 있다.
ex) [id].js

대괄호 표기법은 파일뿐만 아니라 폴더에서도 사용할 수 있다.

a 태그로 페이지를 이동하면 html 전체를 렌더링하지만
link 컴포넌트를 쓰면 페이지 전체를 불러오는 것이 아니라 필요한 데이터만 불러오기 때문에 속도도 빠르고 부드럽다.

외부로 가는 Link 컴포넌트는 a 태그와 똑같이 작동하므로 그냥 무조건 Link 컴포넌트를 사용하면 된다.

import styles from '@/styles/Home.module.css';

export default function Home() {
  return (
    <div>
      <h1>Codeitmall</h1>
      <ul>
        <li>
          <Link href="/products/1">첫 번째 상품</Link>
        </li>
        <li>
          <Link href="/products/2">두 번째 상품</Link>
        </li>
        <li>
          <Link href="/products/3">세 번째 상품</Link>
        </li>
        <li>
          <Link href="https://codeit.kr">코드잇</Link>
        </li>
      </ul>
    </div>
  );
}

useRouter: 쿼리 사용하기

사이트 주소에서 원하는 값을 가져오는 방법

ex) pages/products/[id].js 페이지에서 router.query['id'] 값으로 Params id에 해당하는 값을 가져올 수 있다.
pages/products/[id].js

import { useRouter } from 'next/router';

export default function Product() {
  const router = useRouter();
  const{ id } = router.query;//파일 이름에 있는 그 id임
  
  return <div>Product {id} 페이지</div>;
}

쿼리 string 값 가져오기

param을 가져오는 방법이랑 같다 (router.query)

http://localhost:3000/search?q=티셔츠 이런 식의 경우에서 q 값을 가져오는 방법은 아래와 같다.

import { useRouter } from 'next/router';

export default function Search() {
  const router = useRouter(0);
  const { q } = router.query;
                           
  return (
    <div>
      <h1>Search 페이지</h1>
      <h2>{q} 검색 결과 </h2>
    </div>);
}

useRouter: 페이지 이동하기

router.push()로 페이지 이동

searchForm.js

import { useRouter } from "next/router";
import { useState } from "react";

export default function SearchForm({ initialValue = "" }) {
  const router = useRouter();
  const [value, setValue] = useState(initialValue);

  function handleChange(e) {
    setValue(e.target.value);
  }

  function handleSubmit(e) {
    e.preventDefault();
    if (!value) {
      router.push(`/`);
      return;
    }
    router.push(`search?q=${value}`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="q" value={value} onChange={handleChange} />
      <button>검색</button>
    </form>
  );
}

API 연동하기

@/lib/axios.js

import axios from "axios";

const instance = axios.create({
  baseURL: "https://learn.codeit.kr/api/codeitmall",
});
export default instance;

@/pages/[id].js

import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import axios from "@/lib/axios";
import SizeReviewList from "@/components/SizeReviewList";

export default function Product() {
  const [product, setProduct] = useState();
  const [sizeReviews, setSizeReviews] = useState([]);
  const router = useRouter();
  const { id } = router.query; //파일 이름에 있는 그 id임

  async function getProduct(targetId) {
    const res = await axios.get(`products/${targetId}`);
    const nextProduct = res.data;
    setProduct(nextProduct);
  }
  async function getSizeReviews(targetId) {
    const res = await axios.get(`/size_reviews/?product_id=${targetId}`);
    const nextSizeReviews = res.data.results ?? [];
    setSizeReviews(nextSizeReviews);
  }

  useEffect(() => {
    if (!id) return;

    getProduct(id);
    getSizeReviews(id);
  }, [id]);

  if (!product) return null;

  return (
    <div>
      <h1>{product.name}</h1>
      <img src={product.imgUrl} alt={product.name} />
      <SizeReviewList sizeReviews={sizeReviews} />
    </div>
  );
}

@/pages/index.js

import SearchForm from "@/components/SearchForm";
import ProductList from "@/components/ProductList";
import axios from "@/lib/axios";
import { useEffect } from "react";
import { useState } from "react";

export default function Home() {
  const [products, setProducts] = useState();

  async function getProducts() {
    const res = await axios.get(`/products`);
    const nextProducts = res.data.results;
    setProducts(nextProducts);
  }

  useEffect(() => {
    getProducts();
  }, []);

  return (
    <>
      <h1>Codeitmall</h1>
      <SearchForm />
      <ProductList products={products} />
    </>
  );
}

이런식으로 사용하면 된다.

리다이렉트

리다이렉트란?

사이트를 운영하다면 주소를 바꾸는 경우가 생긴다. (ex)products/1 -> items/1)
next.js에서는 폴더 이름과 링크 주소만 바꿔주면 된다.

방문자 중에 옛날 주소를 북마크한 경우가 있다면 사이트에 접속했을 때 오류가 생길 것이다.
이런 상황에는 products로 시작하는 주소를 모두 items로 바꿔주서 이동시켜주면 된다.
이럴 때 사용하는 것이 리다이렉트이다.

리다이렉트 하는 방법

공식 문서 redirects

  async redirects() {
    return [
      {
        source: '/about', //리다이렉트 처리를 할 주소
        destination: '/', //이동시킬 주소
        permanent: true, //웹 브라우저에게 주소가 바뀌었단 사실을 저장하게 하려면 true로
      },
    ]
  },

이 코드를 참고해서next.config.js를 수정하면 된다.

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  async redirects() {
    return [
      {
        source: '/products/:id',
        destination: '/items/:id',
        permanent: true,
      },
    ]
  },
};

export default nextConfig;

:id는 params

permanent

false : 307
true : 308

커스텀 404 페이지

그냥 pages 폴더에 404.js를 만들면 된다! (매우 간단!)

커스텀 App과 Document

App 컴포넌트

app.js에 코드를 작성하면 모든 페이지에 적용시킬 수 있다.

공통된 레이아웃(헤더 같은)을 구현할 때 app 컴포넌트에 구현하면 된다.

import "../styles/global.css";
import Container from "@/components/Container";
import Header from "@/components/Header";
export default function App({ Component, pageProps }) {
  return (
    <>
      <Header />
      <Container>
        <Component {...pageProps} />
      </Container>
    </>
  );
}

Document 컴포넌트

_document.js는 HTML 코드의 뼈대를 만드는 역할이라고 볼 수 있다.
서버에서 렌더링 할 때만 이 컴포넌트를 실행한다.
클라이언트에서는 실행하지 않음
인터렉티브한 자바스크립트 사용 불가 (useState, useEffect)

Context 활용하기

App 컴포넌트에서 Context를 사용하면 사이트 전체에서 활용할 수 있다.

module.css에서 light라는 클래스를 다른 모듈 css에서 사용하려면 :global이라고 사용해야 한다.

:global(.light) .button{
  color:#f9f9f9;
  background-color:#505050;
}

API 라우팅

Next.js에서는 간단한 백엔드 API를 만들 수 있다.

/pages폴더 아래에 /api라는 폴더를 만들고 여기에 다음과 같은 자바스크립트 파일을 추가하면 된다.

let cart = [];

export default function handler(req, res) {
  if (req.method === 'GET') {
    return res.status(200).json(cart);
  } else if (req.method === 'PUT') {
    cart = req.body;
    return res.status(200).json(cart);
  } else {
    return res.sendStatus(404);
  }
}

이렇게 default export로 리퀘스트 객체(req)와 리스폰스 객체(res)를 파라미터로 받는 함수를 만들면 된다.

위 코드는 GET 리퀘스트를 보냈을 때 cart 배열을 리스폰스로 보내 주고, PUT 리퀘스트를 보냈을 때 cart 배열을 수정하는 간단한 코드이다.

이 API의 주소는 Next.js에서 페이지를 만들었을 때의 주소와 마찬가지이다.
/api/cart.js라는 경로이니까 /api/cart라는 주소로 리퀘스트를 보내면 파일에 있는 핸들러 함수를 실행해서 리스폰스를 보내 주는 형태이다.

웹 브라우저에서 http://localhost:3000/api/cart라는 주소로 접속하거나, API 테스트를 해 보면 아래와 같은 JSON 데이터가 리스폰스로 전달될 것이다.

GET 리퀘스트를 보낼 때

GET http://localhost:3000/api/cart
Content-Type: application/json

GET 리퀘스트를 보내고 받은 리스폰스 예시

[]

PUT 리퀘스트를 보낼 때


PUT http://localhost:3000/api/cart
Content-Type: application/json

[1, 2, 3]

PUT 리퀘스트를 보내고 받은 리스폰스 예시

[1, 2, 3]

리퀘스트 객체를 활용하면 리퀘스트의 헤더나 쿠키 같은 값을 사용해서 다양한 동작을 하도록 만들 수 있다.
Next.js-API Routes

post-custom-banner

0개의 댓글