Dev/JS Family, HTML, CSS

NextJS 공식문서로 공부하기 - 블로그 만들기

youngst 2023. 2. 19. 14:45

Next.js 공식문서

Next.js는 프레임워크이다. 프레임워크란 일반적으로 '특정 응용프로그램을 만들기위한 클래스와 라이브러리로 이루어진 뼈대'라고 설명된다. 그냥 도구만 주어지는 라이브러리와는 조금 다르게, 프레임워크의 뼈대(요구사항)에 맞춰 코드를 구성하면 프레임워크가 직접 코드를 호출하여 애플리케이션을 구동하는 것이라고 나는 이해했다.

 

어쨌든 이러한 프레임워크를 잘 사용하기 위해서는 이 '뼈대'를 잘 이해하는 것이 중요하다. 그리고 이 뼈대를 가장 잘 설명해주는 것은 당연히 공식문서일 것이다. 저번에 Next.js의 원리와 목적을 아주 간단하게 훑어봤으니 이번에는 공식문서의 튜토리얼을 따라 블로그 프로젝트를 진행해보려고 한다.

 

Next.js 시작하기

리액트에 CRA나 Vite가 있는 것처럼 Next.js도 초기 구성 도구를 제공한다. create-next-app을 이용하면 간단하게 Next.js 앱을 시작할 수 있다. 

npx create-next-app@latest

 

Next.js의 라우팅

pages 경로의 폴더와 파일

React에서는 네이티브 Router가 없어 일반적으로 `React-Router`를 세트처럼 구성하곤 했다. 하지만 Next.js는 기본적으로 라우팅을 제공한다. 'pages' 디렉토리에 폴더나 jsx(or js)파일을 넣고 default exporting만 하면 자동으로 라우트 구성을 해준다! 파일의 이름은 해당 페이지의 경로가 된다.

routes.jsx와 수많은 import로부터 해방되었다!!

 

Link 컴포넌트

React-Router의 <Link /> 컴포넌트처럼 Next.js에도 Client-Side Navigating을 지원하는 <Link /> 컴포넌트가 있다. 사용법은 일반적인 a 태그와 비슷하다. React-Router와는 달리 'to' 대신 'href'를 사용한다.

<h1 className={styles.title}>
  Read <Link href="/posts/first-post">this page!</Link>
</h1>

 

Next.js의 마법같은 기능...

Next.js는 기본적으로 page마다 코드 분할이 적용된다. 이는 초기 업로드는 빠르지만 매 페이지마다 다운로드 과정을 거쳐야한다는 단점이 있다. 하지만 여기서 Next.js의 강력한 기능인 Pre-fetching이 작동한다. 수동으로도 Pre-fetch할 수 있지만 현재 보고 있는 페이지에 <Link /> 컴포넌트가 있다면 Next.js는 자동으로 해당 페이지를 미리 다운로드(Pre-fetching)해놓는다!

 

* 이 기능은 Production 모드에서만 작동한다.

 

Next.js의 Assets 관리

images

이미지는 리액트와 마찬가지로 public 폴더에서 관리하면 된다. public에서 관리하는 것이 SEO에도 도움이 된다고 한다. 일반적으로 이미지를 넣기위해서는 <img /> 태그를 사용하지만 이는 다음의 것들을 직접 해결해야한다.

  • 다른 디바이스 사이즈마다 이미지 크기가 맞는지 일일이 확인하기
  • 써드 파티 라이브러리 등을 이용해 이미지를 최적화 하기
  • 뷰포트 안에 들어갈 때 이미지를 로딩하기

Next.js는 <Image /> 컴포넌트를 제공하여 위의 문제들(+a)을 해결한다.

 

Image 컴포넌트

Next.js는 빌드 시간이 아닌 사용자의 요청 즉시 이미지를 최적화 한다. 때문에 이미지가 아무리 많아도 빌드 타임은 증가하지 않는다. 또한 이미지는 뷰포트로 스크롤 될 때 로딩(lazy loading)되기 때문에 이미지가 많더라도 페이지 로딩 속도가 느려지지 않는다.

 

 

Head Tag 수정하기

이전에 언급했듯이 Next.js에는 index.html 파일이 존재하지 않는다. 그렇다면 페이지의 <head>태그는 어떻게 수정할까?

 

이는 Head 컴포넌트로 해결할 수 있다. Head 컴포넌트를 루트 pages 디렉토리의 index.js 파일에 넣으면 Head 태그를 사용할 수 있다(title, link, meta 태그 등). 

 

여기엔 또 다른 장점이 있다. 일반적으로 리액트에서는 <div class="root"> 태그 안에 모든 내용과 라우트를 구성하기 때문에 페이지마다의 head를 변경하기 위해서는 별도로 <head> Element를 수정해 바꾸어주어야했다. 하지만 Next.js에서는 간단하게 페이지마다 별도의 head를 구성하는 것이 가능하다.

import Head from "next/head";

export default function FirstPost() {
  return (
    <>
      <Head>
        <title>First Post</title>
      </Head>
      <h1>First Post</h1>
    </>
  );
}

 

Script 컴포넌트

Script 컴포넌트는 외부 Javascript를 안전하게 가져올 수 있게 해준다. lazy-loading 옵션 등도 제공한다.

export default function FirstPost() {
  return (
    <>
      <Head>
        <title>First Post</title>
      </Head>
      <Script
        src="https://connect.facebook.net/en_US/sdk.js"
        strategy="lazyOnload"
        onLoad={() =>
          console.log(`script loaded correctly, window.FB has been populated`)
        }
      />
      <h1>First Post</h1>
      <h2>
        <Link href="/">← Back to home</Link>
      </h2>
    </>
  );
}

 

CSS 스타일링

Next.js에서는 CSS module을 사용해서 자동으로 유일한 class name을 생성한다. 

자동으로 붙는 Class name

이는 굳이 스타일을 위한 클래스 네이밍 때문에 외부 라이브러리를 꼭 써야할 필요는 없다는 뜻이다! 또한 별도의 설치 없이 SCSS파일을 기본으로 지원한다. 물론 Tailwind CSS 같은 PostCSS 라이브러리나 Styled-Components 같은 CSS-in-JS 라이브러리를 사용하는 것도 가능하다.

 

CSS Module

CSS Module을 사용하기 위해서는 파일 이름이 꼭 .module.css 로 끝나야한다. 그후 적용할 함수 컴포넌트에 CSS 파일을 import한다. 이름은 styles 같은 것으로 한다. 그 후 스타일을 적용할 태그나 컴포넌트에 styles.container 처럼 객체형태로 넣어주면 CSS 스타일이 붙게된다. 이때 Styled-Components처럼 자동으로 Unique한 클래스네임을 생성한다. Next.js는 기본적으로 CSS와 Sass를 지원한다.

 

Global Style

글로벌 스타일링도 가능하다. pages/_app.js 에 아래와 같이 작성하면 된다.

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

_app.js는 모든 페이지를 감싸는 최상위 React 구성요소이다! Root 컴포넌트라고도 볼 수 있다. 이것을 이용하면 페이지를 탐색할 때에도 state를 유지하거나 아래처럼 글로벌 스타일을 추가할 수 있다.

 

** _app.js를 추가하면 dev server를 재시작 해야한다.

 

글로벌 CSS는 오직 _app.js에만 import 할 수 있다. 그렇지 않으면 의도치 않은 페이지까지 글로벌 CSS가 영향을 미칠 수 있다. 우선 최상위 styles 디렉토리를 생성한다. 그 안에 다음의 CSS를 추가한다.

html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  line-height: 1.6;
  font-size: 18px;
}

* {
  box-sizing: border-box;
}

a {
  color: #0070f3;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

img {
  max-width: 100%;
  display: block;
}

그후 CSS를 _app.js에 import 한다.

// `pages/_app.js`
import '../styles/global.css';

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

 

Style 다듬기

우선 `components/layout.module.css`에 다음의 코드를 추가한다.

.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}

.header {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.backToHome {
  margin: 3rem 0 0;
}

다음으로 재사용이 가능한 utility CSS인 `styles/utils.module.css`를 생성한다.

.heading2Xl {
  font-size: 2.5rem;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: -0.05rem;
  margin: 1rem 0;
}

.headingXl {
  font-size: 2rem;
  line-height: 1.3;
  font-weight: 800;
  letter-spacing: -0.05rem;
  margin: 1rem 0;
}

.headingLg {
  font-size: 1.5rem;
  line-height: 1.4;
  margin: 1rem 0;
}

.headingMd {
  font-size: 1.2rem;
  line-height: 1.5;
}

.borderCircle {
  border-radius: 9999px;
}

.colorInherit {
  color: inherit;
}

.padding1px {
  padding-top: 1px;
}

.list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.listItem {
  margin: 0 0 1.25rem;
}

.lightText {
  color: #666;
}

 

layout.js 업그레이드

이제 `components/layout.js`를 아래의 코드로 대체한다.

import Head from 'next/head';
import Image from 'next/image';
import styles from './layout.module.css';
import utilStyles from '../styles/utils.module.css';
import Link from 'next/link';

const name = 'Your Name';
export const siteTitle = 'Next.js Sample Website';

export default function Layout({ children, home }) {
  return (
    <div className={styles.container}>
      <Head>
        <link rel="icon" href="/favicon.ico" />
        <meta
          name="description"
          content="Learn how to build a personal website using Next.js"
        />
        <meta
          property="og:image"
          content={`https://og-image.vercel.app/${encodeURI(
            siteTitle,
          )}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}
        />
        <meta name="og:title" content={siteTitle} />
        <meta name="twitter:card" content="summary_large_image" />
      </Head>
      <header className={styles.header}>
        {home ? (
          <>
            <Image
              priority
              src="/images/profile.jpg"
              className={utilStyles.borderCircle}
              height={144}
              width={144}
              alt=""
            />
            <h1 className={utilStyles.heading2Xl}>{name}</h1>
          </>
        ) : (
          <>
            <Link href="/">
              <Image
                priority
                src="/images/profile.jpg"
                className={utilStyles.borderCircle}
                height={108}
                width={108}
                alt=""
              />
            </Link>
            <h2 className={utilStyles.headingLg}>
              <Link href="/" className={utilStyles.colorInherit}>
                {name}
              </Link>
            </h2>
          </>
        )}
      </header>
      <main>{children}</main>
      {!home && (
        <div className={styles.backToHome}>
          <Link href="/">← Back to home</Link>
        </div>
      )}
    </div>
  );
}

블로그 완성