나는 오늘도 멋있다

Next.js 기본사용방법 및 동작 (튜토리얼 ver) 본문

Web/NextJS

Next.js 기본사용방법 및 동작 (튜토리얼 ver)

나는 오늘도 멋있다 2023. 11. 22. 19:53

하루를 꼬박 작성한글이 날아갔다... 임시저장을 해뒀는데 다른글을 보고오니 날라가버렸다 .... ㅠㅠ 다시썻다.

 

Next.js란?

NextJS는 React 기반의 웹 프레임워크이다. React 애플리케이션을 더쉽게 구축하고 서버 측 렌더링(SSR), 정적 사이트 생성(SSG), API라우팅 등의 기능을 제공며, 여러 빌딩 블록을 제공한다.

 

페이지 시스템
페이지 기반의 라우팅 시스템을 가지고 있다. pages 디렉토리에 있는 각 파일은 하나의 페이지를 나타낸다.(파일 경로 및 파일 이름)
서버측 렌더링(SSR)
기본적으로 서버측 렌더링을 제공하며, 페이지를 서버 측에서 사전렌더링 할 수 있다.
정적 사이트 생성(SSG)
빌드 시간에 페이지를 미리 생성하여 정적 파일로 제공할 수 있다.
클라이언트 측 라우팅
클라이언측 라우팅을 지원하면 React Router 또는 Next.js의 내장 라우터를 사용할 수 있다.
API Routes
pages/api 디렉토리를 사용하여 서버리스 함수를 생성하며, 엔드포인트를 작성 할 수 있다.
정적 파일 제공
public 디렉토리에 있는 파일들은 정적으로 서빙됩니다. 이미지, CSS 파일 등과 같은 정적 자산들은 이 디렉토리에 위치시킬 수 있다.
데이터 프리페칭(Data Fetching)
getStaticProps, getServerSideProps, getInitialProps를 사용하여 페이지의 데이터를 사전에 불러오고 페이지에 주입할 수 있다.
CSS 지원
다양한 CSS 스타일링을 지원하며, 모듈형 CSS, CSS-in-JS, Sass, Less 등을 사용할 수 있습니다.
Webpack 및 Babel 설정
내부적으로 Webpack 및 Babel을 사용하며, 추가적인 설정을 next.config.js 파일에서 직접 구성할 수 있다.
플러그인 시스템
다양한 플러그인을 사용하여 기능을 확장할 수 있다. (이미지 최적화, TypeScript 지원 등의 플러그인을 활용)

 


 

Learn Next.js | Next.js by Vercel - The React Framework

Next.js by Vercel is the full-stack React framework for the web.

nextjs.org

 

튜토리얼 기반으로 하는 학습은 next12 버전을 토대로 진행하여서 13버전과는 다르다. 12버전으로 학습후 13버전을 사용하여 비교하는 글은 따로 다루도록 해야겠다.

 


1. 페이지 기본 경로정의

기본경로 파일구조
좌: index.tsx / 우: 웹상 index.tsx (SSR)

 

기본적은 pages폴더 안에 생성되는 파일 or 다른폴더 들은 URL 칭하게 된다. React와는 다르게 명시적으로 URL을 지정하지 않아도 폴더와 파일을 기반으로 url이 생성되기 때문에 이점은 너무 편한것 같다.  기본적으로 메인페이지는 pages 폴더 안에있는 index.tsx로 인식된다.  만약 위사진처럼 create페이지에 접속하려 한다면 http//:localhost:3000/pages/create-list/create 경로를 통해 이동할수가 있다. 

 

2. 페이지 동적 경로정의

동적경로 파일구조
페이지 이동버튼
공지사항 이동시 보이는 리스트 (SSG)

사용자가 블로그 게시글을 읽으려고 할때, 특정 리스트를 눌렀다면 해당글을 볼수있는 페이지가 나와야하고, 페이지는 사용자가 누른 id값을 받아 해당페이지의 내용을 상세히 보여줘야 한다. 이럴경우에는[id].tsx 파일을 생성하여 사용자가 클릭시 보여줘야 하는 페이지의 id를 넘겨줘서 보여줄수가 있다. 

 

좌: ssg-list.tsx / 우: [id].tsx

 


 

Next.js Components

<Link>
경로가 사용자의 뷰포트에 표시되면 자동으로 미리 가져옵니다.
프리페치는 페이지가 처음 로드될 때 또는 스크롤을 통해 표시될 때 발생합니다.

<LInk> 태그는 프리페치 동작은 정적 경로와 동적 경로에 따라 다르다.
- 정적 경로 :prefetch기본값은 입니다true. 전체 경로가 프리페치되고 캐시됩니다.
- 동적 경로 :prefetch기본값은 자동입니다. 첫 번째loading.js파일이 프리페치되고30s. 이렇게 하면 전체 동적 경로를 가져오는 비용이 줄어들고사용자에게 더 나은 시각적 피드백을 제공하기 위해 즉시 로드 상태를 표시할 수 있습니다.

** 프리페치는 사용자가 경로를 방문하기 전에 백드라운드에서 경로를 미리 로드하는 방법 **
** Link태그는 현재 페이지의 JS와 이동할 페이지의 JS파일만을 로드하여 효율적인 페이지 간 전환을 제공하고, a 태그는 전체 애플리케이션의 JS파일을 로드하여 새로운 페이지를 불러온다.

- 사용법
차이점을 명확하게 보려면, 개발자도구에서 html에 태그에 background 속성을 적용후 페이지를 이동해 보면 알수있다.
// index.js

import Link from "next/link";

export default function Home() {
  return (
        <Link href="/posts/first-post">this page!</Link>
 );
};
//first-post.js

import Link from "next/link";

export default function FirstPost() {
  return (
    <>
    <h1>First Post</h1>
    <Link href="/">Back to home</Link>
    </>
  );
};​
 

Components: <Link> | Next.js

API reference for the <Link> component.

nextjs.org

 

 

Routing: Linking and Navigating | Next.js

Learn how navigation works in Next.js, and how to use the Link Component and `useRouter` hook.

nextjs.org


<Image>
이미지 구성 요소는 <img>자동 이미지 최적화 기능으로 HTML 요소를 확장합니다.

- 크기 최적화: WebP 및 AVIF와 같은 최신 이미지 형식을 사용하여 각 장치에 올바른 크기의 이미지를 자동으로 제공합니다.
- 시각적 안정성: 이미지가 로드될 때 레이아웃이 자동으로 전환되는 것을 방지합니다 .
- 더 빠른 페이지 로드: 이미지는 선택적 흐림 자리 표시자와 함께 기본 브라우저 지연 로딩을 사용하여 뷰포트에 들어갈 때만 로드됩니다.
- 자산 유연성: 원격 서버에 저장된 이미지의 경우에도 주문형 이미지 크기 조정

- 사용법
// index.js

import Image from "next/image";

export default function Home() {
  return (
      <Image
          src="/images/profile.jpg"
          height={144}
          width={144}
          alt="Your Name"
       />
 );
};​
 

Components: <Image> | Next.js

Optimize Images in your Next.js Application using the built-in `next/image` Component.

nextjs.org

 

Optimizing: Images | Next.js

Optimize your images with the built-in `next/image` component.

nextjs.org


<Head>
각페이지의 html의 haed태그를 정의할 수 있다. (메타데이터, 스타일시트. 스크립트 등 추가)

* 스크립트는 메타데이터 외에도 가능한 빨리 로드하고 실행해야하는 스크립트들 같은경우

ps. 이 접근 방식은 작동하지만 이러한 방식으로 스크립트를 포함하면 동일한 페이지에서 가져온 다른 JavaScript 코드와 관련하여 로드될 시점에 대한 명확한 아이디어를 제공하지 않습니다. 특정 스크립트가 렌더링을 차단하고 페이지 콘텐츠 로드를 지연시킬 수 있는 경우 이는 성능에 심각한 영향을 미칠 수 있습니다.

- 사용법

// index.js

import Head from "next/head";

export default function Home() {
  return (
     <div>
      <Head>
        <title>Create Next App</title>
        <meta property="og:title" content="My page title" key="title" />
        <script src="https://connect.facebook.net/en_US/sdk.js" />
      </Head>
      .... 이외코드
      
     </div>
 );
};
 

Components: <Head> | Next.js

Add custom elements to the `head` of your page with the built-in Head component.

nextjs.org

 

 


<Script>
이 스크립트는 애플리케이션의 경로에 액세스할 때 로드되고 실행됩니다 . Next.js는 사용자가 여러 페이지 사이를 이동하더라도 스크립트가 한 번만 로드 되도록 보장합니다.

-사용법
export default function FirstPost() {
  return (
    <Layout>
      <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 Posttt</h1>
      <h2>
        <Link href="/">Back to home</Link>
      </h2>
    </Layout>
  );
}
  • strategy타사 스크립트를 로드해야 하는 시기를 제어합니다. 값은 lazyOnloadNext.js가 브라우저 유휴 시간 동안 이 특정 스크립트를 느리게 로드하도록 지시합니다.
  • onLoad스크립트 로드가 완료된 후 즉시 JavaScript 코드를 실행하는 데 사용됩니다. 이 예에서는 스크립트가 올바르게 로드되었음을 언급하는 메시지를 콘솔에 기록합니다.
권장사항 : 성능에 불필요한 영향을 최소화하기 위해 특정 페이지나 레이아웃에는 타사 스크립트만 포함하는 것이 좋습니다.
 

Components: <Script> | Next.js

Optimize third-party scripts in your Next.js application using the built-in `next/script` Component.

nextjs.org


Next.js Css

CSS Module
css모듈을 사용하기위해서는 module.css가 포함되어야 한다 ex) [name].module.css 

1. 고유한 클래스 이름을 자동으로 생성 (html에서 확인시 <파일이름_클래스이름_고유이름>)
2. 일반적인 선택자를 사용할수없다.(태그 선택자를 시작으로 사용할 수 없다.)
2. 각페이지에 최소한의 Css가 로드되도록 보장
3. 빌드시 JS번들에서 추출되며, css 파일을 생성 한다.

- 사용법
.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.title a {
  color: #0070f3;
}

// error
 a {
  color: #0070f3;
}

 

 

Styling: CSS Modules | Next.js

Style your Next.js Application using CSS Modules.

nextjs.org


 

Global CSS
_app.js 파일에서만 불러와 사용할수가 있다. 다른곳에서 전역 Css를 가져올 수 없다.

- 사용법
// styles/global.css

body {
  font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica',
    'Arial', sans-serif;
  padding: 20px 20px 60px;
  max-width: 680px;
  margin: 0 auto;
}​
// pages/_app.js

import "../styles/global.css";

export default function App({ Component, pageProps }) {
    return <Component {...pageProps} />;
  }
// pages/posts/first-post.js

import Head from "next/head";
import Script from "next/script";
import Link from "next/link";
import Layout from "../../components/layout";

export default function FirstPost() {
  return (
    <Layout>
      <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 Posttt</h1>
      <h2>
        <Link href="/">Back to home</Link>
      </h2>
    </Layout>
  );
}

 

 

Styling: CSS Modules | Next.js

Style your Next.js Application using CSS Modules.

nextjs.org

스타일링 Tip 튜토리얼

 

Learn Next.js | Next.js by Vercel - The React Framework

Next.js by Vercel is the full-stack React framework for the web.

nextjs.org


사전 렌더링 및 데이터 가져오기

Next.js는 기본적으로 모든페이지를 미리 렌더링 한다고 한다. 즉 클라이언트 측 JS를  이용하여 작업을 수행하는 것이 아닌 각 페이지에 대해 미리 HTML을 생성한다는 것 이다. 이러한 사전 렌더링은 SEO에 좋다. 이과정에서도 생성된 각 HTML 파일은 페이지에 필요한 최소한의 JS코드와 연결되고, 브라우저가 페이지를 로드하면 JS코드가 실행되어 페이지가 완전히 대화형으로 만들어진다고 한다. 이과정을 hydration 이라고 칭한다.

 

next.js에서 제공하는 사전렌더링 비교해볼수있는 사이트다. JS를 비활성화 한후에 비교해보자

사전렌더링 발생
https://next-learn-starter.vercel.app/ 
사전렌더링 발생하지 않음
https://create-react-template.vercel.app/

왼쪽: 사전렌더링 / 오른쪽: 사전렌더링

 

Next.js Sample Website

Hello, I’m Shu. I’m a software engineer and a translator (English/Japanese). You can contact me on Twitter. (This is a sample website - you’ll be building a site like this in our Next.js tutorial.)

next-learn-starter.vercel.app

 

 

Building Your Application: Rendering | Next.js

Learn the fundamentals of rendering in React and Next.js.

nextjs.org

 

Next.js 에서는 정적 생성 과 서버 측 렌더링 이라는 두가지 형태의 렌더링이 있다고 한다. 두개의 차이점은 페이지의 HTML을 생성하는 시점에 있다고 한다. 정적생성(SSG)는 빌드시 HTML을 생성하는 사전 렌더링 방법인데, HTML이 각 요청에서 재사용 된다. 보통 내용이 변하지 않는 곳에서 쓰이는 것 같다. 예를 들면 도움말 및 문서, 기업소개 처럼 내용이 정기적으로 업데이트 되지 않는 페이지말이다. 또 하나는 당연히 서버 측 렌더링(SSR)이다. 각요청에 대해 HTML을 생성하는 사전 렌더링 방법이다. 즉 간단하게 정적 생성(SSG): 사용자의 요청보다 먼저 페이지를 미리 렌더링 할수 있고, 데이터 유무에 관계없이 수행 될수 있어야하고 서버 측 렌더링(SSR): 최신상태로 유지해야하며, JS를 사용하여 자주 업데이트 데이터가 있을때 사용하면 될 것 같다. 그렇다면 클라이언트 측 렌더링(CSR)은 SSR보다는 좀다 인터렉티브한 페이지에 사용하면 좋을 것 같다.

개발모드에서는 정적 생성은 각요청에 대해 발생하도록 설계되어 있다고 한다. 프로덕션 단계에서는 당연히 빌드시에 한번에 발생한다.

왼쪽: 정적생성 / 오른쪽: 서버 측 렌더링

 

 

정적 생성(SSG)를 이용하여 페이지를 만들때 단순히 생각하면 html에 글씨를 주입할 수도 있겠지만, 파일 시스템에 액세스, 외부 API, 빌드 시 데이터 베이스를 쿼리할 수도 있다.  그렇다면 어떤식으로 가져와야 할까? 정적생성(SSG)에서 사용할수 있는 함수가 있다.
페이지에서 getStaticProps 함수를 내보내면 Next.js는 반환된 Props를 사용할 수 있다.

getStaticProps() - SSG

- 서버측 에서만 실행됨
- JS 번들에 포함되지 않는다.
- 페이지 에서만 허용됨
- 빌드 타임에 데이터를 불러온다.
- 개발모드에서는 요청시 실행된다.
- 파일시스템, 외부 API, 데이터베이스 쿼리 등에 사용가능하다.
- 쿼리매개변수, HTTP 헤더와 같이 요청시 사용가능한 데이터는 사용할 수 없다.
- 독립적으로 사용해야 한다.( 컴포넌트안에서 사용하면 작동하지 않음)
// root/posts/ssg-ssr.md

---
title: 'When to Use Static Generation v.s. Server-side Rendering'
date: '2020-01-02'
---

We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.

You can use Static Generation for many types of pages, including:

- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation

You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.

On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.

In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.

// root/lib/posts.js

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const postsDirectory = path.join(process.cwd(), 'posts');

export function getSortedPostsData() {
  // Get file names under /posts
  const fileNames = fs.readdirSync(postsDirectory);
  const allPostsData = fileNames.map((fileName) => {
    // Remove ".md" from file name to get id
    const id = fileName.replace(/\.md$/, '');

    // Read markdown file as string
    const fullPath = path.join(postsDirectory, fileName);
    const fileContents = fs.readFileSync(fullPath, 'utf8');

    // Use gray-matter to parse the post metadata section
    const matterResult = matter(fileContents);

    // Combine the data with the id
    return {
      id,
      ...matterResult.data,
    };
  });
  // Sort posts by date
  return allPostsData.sort((a, b) => {
    if (a.date < b.date) {
      return 1;
    } else {
      return -1;
    }
  });
}

// root/pages/index.js

import Head from 'next/head';
import Layout, { siteTitle } from '../components/layout';
import utilStyles from '../styles/utils.module.css';
import { getSortedPostsData } from "../lib/posts"

export function getStaticProps() {
  const data = getSortedPostsData();
  return {
    props: {
      data,
    },
  };
}


export default function Home( {data} ) {
  console.log(data);
  
  return (
    <Layout>
     ...
      <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>
        <h2 className={utilStyles.headingLg}>Blog</h2>
        <ul className={utilStyles.list}>
          {data.map(({ id, date, title }) => (
            <li className={utilStyles.listItem} key={id}>
              {title}
              <br />
              {id}
              <br />
              {date}
            </li>
          ))}
        </ul>
      </section>
    </Layout>
  );
}

해당코드는 함수를 이용해 마크다운 문법을 라이브러리로 해석하고, 데이터를 가공하고 정렬하여 index.js페이지에서 getStaticProps() 함수를 이용하여 가공된 데이터를 리턴하고 Home 컴포넌트로 전달하고 있다. 즉 Home 컴포넌트는 getStaticProps()의 리턴값을 인식하고 데이터를 사용하는 것 이다. 상황에 따라 async/await을 getStaticProps() 함수에적용하여 외부 API, 데이터베이스 쿼리를 사용하면 될것같다.
 

Data Fetching: getStaticProps | Next.js

Fetch data and generate static pages with `getStaticProps`. Learn more about this API for data fetching in Next.js.

nextjs.org


getServerSideProps() - SSR

- 서버측 에서만 실행됨
- 요청시 실행됨
- JSON을 반환한다.
- 페이지에서만 사용됨
- 독립적으로 사용해야 한다.( 컴포넌트안에서 사용하면 작동하지 않음)
- 데이터를 가져와 사용하는 사전 렌더링 페이지에서만 사용 해야 한다.

// root/pages/index.tsx

import Head from 'next/head';
import styles from '@/styles/index.module.css';
import Navbar from '@/components/Navbar';
import type { InferGetServerSidePropsType, GetServerSideProps } from 'next';


interface BlogItems{
  title: string;
  content: string;
}

export const getServerSideProps: GetServerSideProps<{data: BlogItems[]}> = (async () => {
  const res = await fetch('http://localhost:3000/api/list');
  const data: BlogItems[] = await res.json();
  return { props: { data } }});

export default function Home( {data} : InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <>
      <Head>
        <title>blog-home</title>
        <meta name="description" content="next.js blog test" />
      </Head>
      <header className={styles.layoutHeader}>
          <Navbar/>
      </header>
      <main className={styles.layoutMain}>
        { data && data.map((val, idx)=>{
          return(
            <div key={idx} className={styles.itemsBox}>
              <h3>제목: {val.title}</h3>
              <p>내용: {val.content}</p>
            </div>
          );
        })}
      </main>
    </>
  )
}

// root/pages/api/list.ts


import type { NextApiRequest, NextApiResponse } from 'next';
import fs from "fs";
import path from "path";
const dataFilePath = path.resolve(process.cwd(), 'src/pages/api/db.json');

interface BlogItems{
  title: string;
  content: string;
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<BlogItems | string>
) {

  if(req.method === "GET"){
    const data = fs.readFileSync(dataFilePath, 'utf-8');
    const jsonData: BlogItems = JSON.parse(data);
    res.status(200).json( jsonData )
  } else if(req.method === "POST"){
    const newData = req.body;
    const currentData = JSON.parse(fs.readFileSync(dataFilePath, 'utf-8'));
    const updateData = [...currentData, newData];
    fs.writeFileSync(dataFilePath, JSON.stringify(updateData));
    res.status(200).json("성공");
  } else if(req.method === "DELETE"){
    
  } else if(req.method === "PATCH"){

  }
}

// root/pages/api/db.json


import type { NextApiRequest, NextApiResponse } from 'next';
import fs from "fs";
import path from "path";
const dataFilePath = path.resolve(process.cwd(), 'src/pages/api/db.json');

interface BlogItems{
  title: string;
  content: string;
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<BlogItems | string>
) {

  if(req.method === "GET"){
    const data = fs.readFileSync(dataFilePath, 'utf-8');
    const jsonData: BlogItems = JSON.parse(data);
    res.status(200).json( jsonData )
  } else if(req.method === "POST"){
    const newData = req.body;
    const currentData = JSON.parse(fs.readFileSync(dataFilePath, 'utf-8'));
    const updateData = [...currentData, newData];
    fs.writeFileSync(dataFilePath, JSON.stringify(updateData));
    res.status(200).json("성공");
  }
}


튜토리얼 과정에서는 SSR을 따로 예시가 없어서 다른 블로그와 공식문서를 참고해서 작성해봤다. 데이터 파일로는 .json을 이용하여서 사용자가 글을쓰면 db.json에 입력이되고, 입력된 데이터를 index.tsx에서 api/list.ts에 요청을 보내 데이터를 가지고 오고 있다. 

 

Data Fetching: getServerSideProps | Next.js

Fetch data on each request with `getServerSideProps`.

nextjs.org

 

 

솔직히 내가 작성한 글이지만 너무 복잡하다. next12 와 next13이 차이점들 있다보니깐 얼릉 13을 써보고 싶은 마음에 글의 요점이 없기도 하다. 기본적으로 next.js의 사용방법과 전체적인 틀을 알고싶었기에 깊게 보지는 않았다. 해당 글을 기반으로 13을 사용할때는 좀더 정확한 내용과 간단한 내용으로 작성을 해야겠다.
https://github.com/JangIkIk/Next/tree/main/nextjs12-blog