[강의] Next.js로 웹사이트 만들기

김하은·2024년 1월 8일
0

코드잇 강의 정리

목록 보기
59/60

라우팅

  • Next.js에서는 링크를 연결하는데 <a> 태그 대신에 <Link> 컴포넌트를 사용함
  • <a> 태그를 사용하면 페이지를 이동할 때 페이지 전체를 다시 로딩하기 때문에 속도가 느리고, 빈 화면이 잠깐 보이면서 깜빡거림이 생김
  • <Link> 컴포넌트는 Next.js에서 내부적으로 여러 가지 최적화를 해주기 때문에 빠르고 부드러운 페이지 전환이 가능함
import Link from 'next/link';

export default Page() {
  return <Link href="/">홈페이지로 이동</Link>;
}

useRouter() Hook

쿼리 사용하기

  • router.query 값을 사용하면 페이지 주소에서 Params 값이나 쿼리스트링 값을 참조할 수 있음
  • 예를 들면 pages/products/[id].js 페이지에서 router.query['id'] 값으로 Params id에 해당하는 값을 가져올 수 있음
import { useRouter } from 'next/router';

export default function Product() {
  const router = useRouter();
  const id = router.query['id'];

  return <>Product #{id} 페이지</>;
}
  • /search?q=티셔츠와 같은 주소로 들어왔을 때 router.query['q'] 값으로 쿼리스트링 q에 해당하는 값을 가져올 수 있음
import { useRouter } from 'next/router';

export default function Search() {
  const router = useRouter();
  const q = router.query['q'];

  return <>{q} 검색 결과</>;
}

페이지 이동하기

  • router.push() 함수를 사용하면 코드로 페이지를 이동할 수 있음
import { useState } from 'react';
import { useRouter } from 'next/router';

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

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

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

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

리다이렉트

  • next.config.js 파일을 수정하면 특정 주소에 대해서 리다이렉트할 주소를 지정할 수 있음
  • 예를 들어서 /products/:id라는 주소로 들어오면 /items/:id라는 주소로 이동시켜 줄 수 있음
  • 이때 permanent라는 속성으로 상태 코드를 정할 수 있는데 permanent: false로 하면 307 Temporary Redirect를 하고, permanent: true로 하면 브라우저에 리다이렉트 정보를 저장하는 308 Permanent Redirect를 할 수 있음

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

module.exports = nextConfig;

커스텀 404 페이지

  • 존재하지 않는 주소로 들어올 경우에 Next.js에서는 기본적으로 404 페이지를 보여 줌
  • 내가 원하는 404 페이지를 보여주려면 pages/404.js 파일을 만들고 일반적인 페이지처럼 구현하면 됨

커스텀 App

  • 모든 페이지에 공통적으로 코드를 적용하고 싶다면 커스텀 App 컴포넌트를 수정하면 됨
  • pages/_app.js 파일에 있는 컴포넌트인데 이 컴포넌트에 사이트 전체에서 보여 줄 컴포넌트나 전체적으로 적용할 리액트 컨텍스트를 적용할 수 있음
  • 사이트 전체에 적용할 CSS 파일도 여기서 임포트할 수 있음
  • 커스텀 App 컴포넌트의 Props는 ComponentpageProps가 있는데 우리가 만든 페이지들이 Component Prop으로 전달되고 여기에 내부적으로 필요한 Props는 pageProps라는 값으로 전달됨

커스텀 Document

  • pages/_document.js 파일에 있는 Document 컴포넌트는 HTML 코드의 뼈대를 수정하는 용도로 사용함
  • 코드는 React 컴포넌트이지만 일반적인 컴포넌트처럼 동작하지는 않기 때문에 useStateuseEffect처럼 브라우저에서 실행이 필요한 기능들은 사용할 수 없음
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html lang="ko">
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

사이트 완성하기

<Image> 컴포넌트

  • Next.js에서는 이미지를 사용할 때 Next.js 서버를 한 번 거쳐서 이미지 최적화를 한 다음 사용할 수 있도록 해 줌
  • 그래서 되도록이면 <img> 태그보다는 <Image> 컴포넌트를 사용하는 걸 권장함
  • <Image> 컴포넌트는 next/image에서 불러와서 사용함
  • 이때 반드시 크기가 있어야 하는데 widthheight 값을 지정하거나 또는 fill이라는 Prop을 사용할 수 있음

width와 height를 사용하는 경우

import Image from 'next/image';

export default function Page() {
  return (
    <>
      <Image
        src="/images/product.jpeg"
        width={300}
        height={300}
        alt="상품 이미지"
      />
    </>
  );
}

fill을 사용하는 경우

  • 이미지 크기를 유연하게 지정해야 할 때는 fill을 사용gka
  • 이때 부모 요소에서 position: relative와 같이 포지셔닝해야 함
  • fill을 사용할 경우 absolute 포지션으로 배치되기 때문에, "가장 가까운 포지셔닝이 된" 조상 요소에 꽉 차도록 이미지가 배치됨
  • 만약 이미지의 비율이 깨지는 것을 막고 싶다면 object-fit: cover 속성으로 이미지를 크롭하면 됨
import Image from 'next/image';

export default function Page() {
  return (
    <>
     <div
        style={{
          position: 'relative',
          width: '100%',
          height: '300px',
        }}
      >
        <Image
          src="/images/product.jpeg"
          fill
          alt="상품 이미지"
          style={{
            objectFit: 'cover',
          }}
        />
      </div>
    </>
  );
}

<Head> 컴포넌트

  • next/head에서 불러와서 <Head> 컴포넌트 안에 <head> 태그에 넣고 싶은 코드를 작성하면 됨
import Head from 'next/head';

export default function Page() {
  return (
    <>
      <Head>
        <title>페이지 제목</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      ...
    </>
  );
}
  • 만약 사이트 전체에 공통으로 적용하고 싶다면 /pages/_app.js 파일에서 <Head> 컴포넌트를 활용하면 됨
import Head from 'next/head';

export default function App({ Component, pageProps }) {
  return (
    <>
      <Head>
        <title>사이트 기본 제목</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Component {...pageProps} />
    </>
  );
}

구글 폰트 적용하기

  • Next.js에서는 구글 폰트를 위한 기능도 제공함
  • 구글 폰트를 편하게 쓰는 것뿐만 아니라 여러 가지 최적화도 함께 제공함
  • subsets는 폰트에서 영문, 한글 이런 식으로 사용할 글자들만 골라서 사용할 때 쓰는 건데, 만약에 영문만 사용하는 폰트라면 ['latin']과 같이 써 주면 됨
// pages/_app.js
import { Noto_Sans_KR } from '@next/font/google';

const notoSansKR = Noto_Sans_KR({
weight: ['400', '700'],
subsets: [],
});
  • 폰트를 적용하려면 notoSansKR 객체의 className 프로퍼티를 사용할 수 있음
<main className={notoSansKR.className}>
  ...
</main>
  • 아니면 Next.js에서 제공하는 Head 컴포넌트를 활용해서 전역 스타일로 적용할 수도 있음
<Head>
  <style>{`
    html {
      font-family: ${notoSansKR.style.fontFamily}, sans-serif;
    }
  `}</style>
</Head>
  • 폰트를 적용하고 개발자 도구를 열어서 Network 탭의 세부 탭인 Font 탭을 살펴보면 구글 사이트가 아니라, 우리 사이트에서 폰트 파일을 받아 오는 걸 알 수 있는데 그래서 초기 로딩 속도가 훨씬 빨라지는 것임

개발모드, 빌드, 실행 그리고 배포

개발 서버 켜기

  • 프로젝트 폴더에서 터미널을 연 다음, 아래 명령을 입력해서 개발 서버를 실행할 수 있음
  • 개발 모드이기 때문에 새로고침 없이도 수정 사항을 그때 그때 확인할 수 있음
    npm run dev

빌드하기

  • Next.js 프로젝트를 배포하려면 우선 실행 가능한 형태로 코드를 변환한 다음에, 서버에서 실행을 해야 함
  • 이때 코드를 변환하는 과정을 "빌드"라고 함
  • 빌드를 하려면 프로젝트 폴더에서 터미널을 연 다음, 아래 명령어를 입력하면 됨
    npm run build
  • 그럼 .next 폴더에 실행에 필요한 파일들이 생성됨

실행 하기

  • 프로젝트를 빌드했다면 이제 실행할 수 있음
  • 마찬가지로 프로젝트 폴더에서 터미널을 연 다음, 아래 명령어를 입력하면 됨
    npm run start

배포하기

  • Vercel(https://vercel.com/)이라는 서비스를 이용하면 Next.js 프로젝트를 간편하게 배포할 수 있음
  • GitHub 리포지토리와 연동해 놓으면, 리포지토리가 업데이트될 때마다 빌드와 실행 과정을 Vercel이 알아서 수행함
profile
아이디어와 구현을 좋아합니다!

0개의 댓글