그럼에도 불구하고

👨‍💻

[Next.js] Next.js에 대해 알아보자 본문

Next.js/Next.js basics

[Next.js] Next.js에 대해 알아보자

zenghyun 2023. 10. 17. 21:00

 

Next.js에 대해 알아보겠습니다. :)

 

 

 

🧑🏻‍💻 Next.js란?

Next.js는 React로 만드는 서버사이드 렌더링 프레임워크입니다. React는 클라이언트 사이드 렌더링을 수행하지만, Next.js는 서버 사이드 렌더링을 하게 됩니다.

 

기존의 클라이언트 사이드 렌더링에서는 아래와 같은 문제점이 있습니다.

 

1. 클라이언트 렌더링의 경우 사용자는 모든 js 파일이 로드된 후에 웹을 볼 수 있습니다. 만약, js 파일의 용량이 클 경우 사용자는 많은 시간을 대기해야 합니다. 

2. SEO (검색 엔진 최적화) 문제가 있습니다. 클라이언트 사이드의 경우 js가 로드되지 않은 경우 아무런 정보를 볼 수 없습니다. 구글의 검색엔진의 경우 js가 로드되지 않은 페이지를 검색 엔진으로 스캔하기 때문에 검색에 아무 페이지도 나오지 않게 됩니다. 

 

이 두 가지 문제를 해결할 수 있는 게 서버 사이드 렌더링입니다. 

 

첫 번째 문제는 서버에서 js를 로딩하여 클라이언트 측에서는 js를 로딩하는 시간이 줄어들게 되고, 

두 번째 문제는 검색 엔진이 js를 읽는 것이 아닌 서버 측에서 js, html, css를 만들어 콘텐츠를 직접 업로드하기 때문에 검색 엔진에 해당 웹 사이트가 노출될 수 있습니다.

 

또한 meta 태그를 자유롭게 추가하여 SEO를 용이하게 할 수 있습니다.

🧑🏻‍💻 Next.js의 주요 기능

📌 Hot reloading

개발 중 저장되는 코드는 자동으로 갱신(새로고침)됩니다. 

📌 Automatic routing 

pages 폴더에 있는 파일은 해당 파일 이름으로 라우팅 됩니다. 

public 폴더도 pages의 폴더와 동일하게 라우팅 할 수 있습니다. 그러나 모든 사람이 페이지에 접근할 수 있기 때문에 지양해야 합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 |- /pages 
 |
 |-- index.js       => zenghyun.com/
 |-- about.js       => zenghyun.com/about
 |
 |
 |-- /products
 |  |
 |  |-- index.js      => zenghyun.com/products
 |  |-- [id].js       => zenghyun.com/products/1
 |
 |-- /clients 
    |
    |-- index.js      => zenghyun.com/clients
    |-- [id]          
        |
        |-- index.js                => zenghyun.com/client/zenghyun
        |-- [clientprojectid].js    => zenghyun.com/clients/zenghyun/project1
cs

📌 Server side rendering 

Server side rendering을 합니다. Client side rendering과 다르게 SSR을 한 페이지의 페이지 소스보기를 클릭하면 내부에서 소스를 볼 수 있습니다.

📌 Code Splitting

dynamic import를 이용하면 손쉽게 코드 스플리팅이 가능합니다. 

코드 스플리팅이란 내가 원하는 페이지에서 원하는 자바스크립트와 라이브러리를 렌더링 하는 것을 말합니다. 모든 번들 (chunk.js)이 하나로 묶이지 않고, 각각 나뉘어 좀 더 효율적으로 자바스크립트 로딩 시간을 개선할 수 있습니다. 

 

👉 React 18이후 React.lazy와 Suspense를 사용하면 CSR에서도 코드 스플리팅이 가능합니다.

📌 손쉽게 사용 가능한 Typescript 

타입스크립트 활용을 위해 웹팩을 다루거나 바벨을 만질 필요가 없습니다. 타입스크립트를 설치하고 

$ typescript @types/node @types/react 
$ yarn run dev or npm run dev

만 터미널에 입력하면 tsconfig, next-end.d.ts가 생성되어 타입스크립트를 사용할 수 있습니다. :)

📌 _document.tsx

meta 태그를 정의하거나, 전체 페이지에 관여하는 컴포넌트입니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from "next/document";
export default class CustomDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
         // 모든페이지에 아래 메타테크가 head에 들어갑니다.
          <meta property="custom" content="zenghyun" />
        </Head>
        <body>
          <Main />
        </body>
        <NextScript />
      </Html>
    );
  }
}
cs
  • 이곳에서 console을 사용하면 서버에서만 보이고 클라이언트에서는 볼 수 없습니다. 
  • render 요소는 반영하지만 페이지 구성 요소만 반영되고 js는 반영하지 않기 때문에 console은 따로 보이지 않습니다. 즉, componentDidMount 같은 훅도 실행되지 않습니다. 

📌 _app.tsx 

1
2
3
4
5
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}
 
export default MyApp;
cs

 

  • 이곳에서 렌더링 하는 값은 모든 페이지에 영향을 주게 됩니다.
  • 최초로 실행되는 파일이 _app.tsx이며, 클라이언트 보여줄 전체 컴포넌트를 나타낼 공통적인 레이아웃으로 최초 실행되며, 내부 컴포넌트들을 실행합니다.
  • 내부에 컴포넌트가 있따면 전부 실행하고 html의  body로 구성됩니다.
  • ComponentpageProps를 인자로 받습니다.
    • 여기서 props로 받은 Component는 요청한 페이지를 나타냅니다. GET  /  요청을 보냈다면, Component에서는 /pages/index.js 파일이 props로 내려오게 됩니다.
    • pageProps는 페이지 getInitialProps를 통해 내려받은 props들을 말합니다.
  • 그 후에 _document.tsx가 실행됩니다.
  • 페이지를 업데이트 하기 전에 원하는 방식으로 페이지를 업데이트하는 통로입니다.
  • _app.tsx에서 console.log 실행시 client, server 둘 다 콘솔에서 결과를 볼 수 있습니다.

📌 Link 사용하기 

페이지를 이동할 때 a 태그를 자주 사용하지만, React와 Next.js에서는 사용하지 않습니다.

 

a태그를 사용하게 되면 처음 페이지 진입 시 번들 파일을 받고, a 태그에 의해 라우팅 되면 다시 번들 파일을 받기 때문입니다. 또한 redux를 사용할 경우 store의 state 값이 사라지는 현상도 발생하게 됩니다. 그렇기 때문에 a 태그는 전혀 다른 사이트로 페이지를 이동시켜 다시 돌아오지 않을 경우에만 사용하고, 그 이외에는 모두 Link 태그를 사용합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import Link from "next/link";
 
const ClientsPage = () => {
  const clients = [
    { id: "zenghyun"name"Zenghyun" },
    { id: "manuel"name"Manuel" },
  ];
  return (
    <div>
      <h1>The Clients Page</h1>
      <ul>
        {clients.map((client) => (
          <li key={client.id}>
            {/*  아래처럼 작성하는 것은 하드코딩 */}
            {/* <Link href={`/clients/${client.id}`}>{client.name}</Link> */}
            {/* 아래와 같이 작성할 수도 있다. */}
            <Link
              href={{
                pathname: "/clients/[id]",
                query: { id: client.id },
              }}
            >
              {client.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
};
 
export default ClientsPage;
 
cs

📌 Dynamic Route (url) 

가변적으로 변하는 url에 대해 동적 url을 지원합니다.  []  문법을 통해 동적 페이지를 생성하는 동적 url을 만들 수 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// pages/[id].tsx
 
import { useRouter } from "next/router";
 
export default () => {
  const router = useRouter();
 
  return (
    <>
      <h1>post</h1>
      <p>postid: {router.query.id}</p>
    </>
  );
};
 
cs

위의 코드처럼 작성하게 되면 localhost:3000/zenghyun 으로 접속 시 postId가 zenghyun이 됩니다.

pages/[값].tsx 왼쪽 페이지의 구조 값은  router.query.값  과 동일합니다.

 

📌 Optional catch all routes 

  • dynamic route를 사용하고 싶지 않을 때는 dynamic page를 optional 하게 주는 문법을 사용하면 됩니다.
  • [...page].tsx와 같은 형식으로 파일을 만들면 됩니다. [page].tsx 와 다른 점은 router.query.page 의 값이 배열로 담긴다는 것입니다.
  • 예를 들어 page/test/[...page].tsx 가 경로일 때, url을 /test/1/2/3으로 들어왔다면 router.query.page의 값은 ['1', '2', '3']입니다.
  • router.query.page의 타입은 undefined, string, string []으로 페이지 변경에 따라 보여줄 컴포넌트가 다르다면 이 세 타입에 대한 조건을 모두 지정해야 합니다. 

📌 Prefetching 

백그라운드에서 페이지를 미리 가져옵니다. 기본값은 true이며 <Link /> 뷰포트에 있는 모든 항목 (초기 또는 스크롤)이 미리 로드됩니다. 정적 생성을 사용하는 JSON 페이지는 더 빠른 페이지 전환을 위해 데이터가 포함된 파일을 미리 로드합니다.

 

예를 들어 Link 컴포넌트에서 <Link prefetch href="...">  형식으로 prefetch 값을 전달해 주면 데이터를 먼저 불러온 후에 라우팅을 시작합니다.

 

📌 next/router 사용하기 

react의 react-router-dom과 사용 방법은 유사합니다. 

link에 있는 prefetch 기능도 사용 가능합니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useEffect } from "react";
import { useRouter } from "next/router";
import posts from "../posts.json";
 
const PostPage = () => {
  const router = useRouter();
 
  const post = posts[router.query.id as string];
  if (!post) return <p>noting</p>;
 
  useEffect(() => {
    router.prefetch("/test");
  }, []);
 
  return (
    <>
      <h1>{post.title}</h1>
      <h1>{post.content}</h1>
      <button onClick={() => router.push("test")}>go to Test</button>
    </>
  );
};
 
export default PostPage;
cs

 

 

Comments