본문 바로가기
스파르타코딩클럽/내일배움캠프

[TIL] 내일배움캠프 React 과정 2023.01.31_Next.js

by heereal 2023. 1. 31.

Today I Learned

  • Next.js 기초 강의 수강
  • Throttling and Debouncing 강의 수강

 


Next.js

Next.js란?

  • Next.js는 웹 개발자에게 필요한 다양한 기능을 제공해 주는 리액트 프레임워크
  • 개발 환경 설정이 쉽고 간단하다.
  • SEO(Search Engine Optimization)을 위한 SSR(Server-Side rendering)을 지원한다.
  • 초기 로딩 속도 개선을 위한 자동 code splitting(코드 분할)을 지원한다.

 

Next.js 프로젝트 시작하기

npx create-next-app

 

 

개발 모드와 프로덕션 모드

// package.json
...
"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
},
...
  • dev - next dev 개발 모드로 프로젝트를 실행 (Hot reloading 등 개발 환경에 유용한 기능들 제공)
  • build - next build 제품 배포 용도로 프로젝트를 빌드
  • start - next start build를 통해 만들어진 프로젝트를 실행하는 명령어. 실제 운영 서버에서 배포되는 환경과 동일.

 

_App.js

import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp
  • 각각의 페이지가 렌더링 될 때 가장 먼저 실행되는 파일
  • 활용 예시
    • 공통된 레이아웃 페이지 작성 (Header, Footer 등)
    • 글로벌 CSS 적용

 

<Head> 태그

import "../styles/globals.css";
import Head from "next/head";

function MyApp({ Component, pageProps }) {
  return (
    <div>
      <Head>
        <title>테스트</title>
      </Head>
      <h2>Header</h2>
      <Component {...pageProps} />
      <h2>Footer</h2>
    </div>
  );
}

export default MyApp;

`create-react-app` 으로 만들어진 프로젝트와는 다르게, next.js는 눈에 보이는 html 파일이 따로 존재하지 않는 100% 자바스크립트로 이루어진 프로젝트이다. 그렇기 때문에 기존 head 태그에 스크립트나 메타 태그를 추가하기 위해서는 next에서 제공하는 <Head> 컴포넌트를 사용해야 한다.

 

 

Static File Serving

Public 폴더 안에 파일을 추가하면 정적 파일을 쉽게 가져올 수 있다. 예를 들면 public 폴더에 test.json 파일을 추가하고 /test.json으로 접근하면 해당 파일을 가져올 수 있다.

 

 

Next.js에서의 페이지란?

// pages/about.js (1)
export default function About() {
	return <div>About Page</div>
}

// pages/about/index.js (2)
export default function About() {
	return <div>About Page</div>
}

// pages/index.js (3)
export default function Index() {
	return <div>Index Page</div>
}

pages 폴더 안에 있는 react component를 의미한다. 1, 2 두 가지 방법 모두 파일 기반 라우팅을 이용하여 페이지를 만들 수 있다.

 

 

페이지 이동하는 두 가지 방법

import Link from 'next/link'
import { useRouter } from 'next/router';

export default function Home() {
  
  const router = useRouter();

  return (
      <div>
        <Link href={`/about`}>About</Link>
        <div onClick={() => {
          router.push("/profile")
        }}>Profile</div>
      </div>
  )
}

 

 

Dynamic Routes

// pages/post/[id].tsx
import { useRouter } from 'next/router'

const Post = () => {
  const router = useRouter()
  const { id } = router.query

  return <p>Post: {id}</p>
}

export default Post

router를 콘솔로 찍어 보면 이렇게 생겼는데 router.query로 접근하여 id를 가져올 수 있는 것이다.

 

 

API Routes

Next.js 프로젝트에서 Node 서버 없이 api를 만들어 배포할 수 있도록 만들어주는 기능이다. DB에 접근하거나, 시크릿 환경 변수에 접근하는 등에 사용할 수 있다.

 

또한 pages/api 아래 있는 파일들은 yarn build를 통해서 프로젝트가 빌드 될 때 클라이언트 번들에 포함되지 않는데 Next.js가 파일 용량을 자동으로 최적화해 주는 좋은 사례이다.

 

// pages/api/hello.js

export default function handler(req, res) {
  res.status(200).json({ name: 'John Doe' })
}

pages/api/* 아래에 api를 만들 수 있다. /api/hello로 접근 가능하다.

 

 

Dynamic API Route

// pages/api/test/[id].js

export default function handler(req, res) {
  const { query } = req;
  const { id } = query;

  res.status(200).json({ name: "test", id });
}

 

 

MPA부터 SSR까지 흐름 알아보기

 MPA (Multi-Page-Application) 

/about → about.html
/profile → profile.html

원시적인 서버 사이드 렌더링 방식이다. 페이지 이동 시나 렌더링 시 깜빡거려 유저 사용성 저하하는 경우가 있기 때문에 이를 보완하기 위해서 CSR 방식의 React를 이용해서 SPA를 만들기 시작했다.

 

 

 SPA (Single-Page-Application) - CSR (Client-Side Rendering) 

React를 이용하여 더이상 새로고침이나 깜빡거림이 없어 유저 사용성 개선되었으나 자바스크립트 번들 사이즈가 커짐에 따라 초기 로딩 속도가 증가한다는 단점이 생기기도 했다. 이를 보완하기 위해서 Code Splitting 개념이 나왔다.

 

다만, CSR는 HTML 파일에 해당 사이트에 정보가 부족하여 SEO 최적화에 어려움이 있었다. 이로 인해 대두된 것이 바로 SSR이다.

 

Code Splitting (Lazy-Loading)이란?

현재 꼭 필요한 곳에서만 자바스크립트 파일을 로딩하는 방식으로 하나로 번들된 코드를 여러 코드나 컴포넌트로 분리해서, 지금 당장 필요한 코드가 아니라면 나중에 필요할 때 불러와서 사용할 수 있다.

 

 

 SSR(Server-Side Rendering) or SSG(Static-Site Genaration) 

  • Static-Site-Generation : HTML을 빌드 타임에 각 페이지별로 생성하고 해당 페이지로 요청이 올 경우 이미 생성된 HTML 문서를 반환한다. (빌드타임)
  • 마케팅 페이지, 블로그 게시물, 제품의 목록과 같이 정적 생성된 정보를 각 요청에 동일한 정보로 반환하는 경우에 사용한다.

 

  • Server-Side-Rendering : 요청이 올 때 마다 해당하는 HTML 문서를 그때그때 생성하여 반환한다. (런타임)
  • 항상 최신 상태를 유지해야하는 웹 페이지나, 분석 차트, 게시판 등 사용자의 요청마다 동적으로 페이지를 생성해 다른 내용을 보여주어야 하는 경우에 사용한다.

 

출처

https://velog.io/@longroadhome/FE-SSRServer-Side-Rendering-그리고-SSGStatic-Site-Generation-feat.-NEXT를-중심으로#next-js-에서의-ssr-및-ssg-개념

https://narup.tistory.com/235

 

 

Data Fetching part 1 (introduction/SSR)

Rendering이란?

  • Javascript를 이용해서 HTML 코드를 만드는 행위로, 기존 SPA를 만들 때 사용하던 CSR에서의 rendering 표현에 가깝다.

Pre-rendering이란?

Client에 HTML이 이미 로드가 된 이후가 아닌, 이전에 Javascript를 이용해 HTML을 만드는 것을 말한다. 언제 HTML을 만드느냐에 따라서, Server-Side Rendering과 Static-Site generation으로 나뉜다.

  • Build 할 때 rendering 된다면? Static-Site generation (SSG)
  • runtime에 rendering 된다면? Server-Side Rendering (SSR) 
  • runtime이란? 애플리케이션이 빌드 및 배포된 후 사용자의 요청에 대한 응답으로 애플리케이션이 실행되는 기간을 말한다.

 

getServerSIdeProps으로 SSR 구현하기

// pages/ssr/[id].js

import { useRouter } from "next/router";

const Post = ({ post }) => {
  const router = useRouter();
  const { id } = router.query;

  return (
    <div>
      Post: {id}
      <h2>{post.title}</h2>
      <p>{post.body}</p>
    </div>
  );
};

export default Post;

export async function getServerSideProps(context) {
  const { params } = context;
  const { id } = params;

  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${id}`
  );
  const post = await response.json();

  return {
    props: {
      post,
    }, // will be passed to the page component as props
  };
}
  • getServerSideProps 함수는 Server에서만 실행된다. Browser에서는 실행되지 않는다.
  • getServerSideProps 함수는 runtime에서만 실행된다.
  • getServerSideProps에서는 context 객체를 통해, Post Page에서는 next router를 통해 Url Query 파라미터에 접근 가능하다.
  • getServerSideProps의 return 값은 Post page의 props로 전달된다.

 


unshift()

const array1 = [1, 2, 3];

console.log(array1.unshift(4, 5));
// Expected output: 5

console.log(array1);
// Expected output: Array [4, 5, 1, 2, 3]

 

useState Lazy initialization

const [count, setCount] = useState(() =>
  Number.parseInt(window.localStorage.getItem(cacheKey)),
)

useState에 직접적인 값 대신에 함수를 넘기는 것을 게으른 초기화(Lazy Initialization)라고 한다. react 공식 문서에서는, 이러한 게으른 초기화를 초기 값이 복잡한 연산을 포함할 때 사용하라고 되어 있다. 게으른 초기화 함수는 오직 state가 처음 만들어질 때만 실행된다. 이 후에 다시 리렌더링이 된다면, 이 함수의 실행은 무시된다.

 

다시 말해, useState는 그 함수가 처음 렌더링 될 때 작동하며, 이는 count state의 초기값을 만든다. setCount가 실행되면, 전체 함수가 다시 실행되며, count의 값을 업데이트한다. 이는 count의 값이 변경될 때마다 리 렌더링을 발생시킨다. 다시말해, 이 초기 값은 다시 쓰일 일이 없게 된다.

 

따라서, 리 렌더링이 발생할 때 마다 localStorage의 값을 읽지만, 오직 우리는 딱 최초 렌더링 시에만 해당 값이 필요하므로, 이는 필요 없는 계산을 계속해서 하게 되는 것이다. 예제에서는 게으른 초기화를 하기 때문에 불필요한 계산을 막게 된다.

 

그렇다면 언제 게으른 최적화를 써야 할까? 이는 상황에 따라 다르다. 문서에서는 '비싼 비용의 계산'이 필요할 때 쓰라고 되어 있다. 앞선 예와 같이 localStorage의 접근,  map, filter, find 등의 배열을 조작하는 것들이 그 예가 될 수 있다. 일반적으로 함수를 통해서 값을 구해야 한다면, 이는 비싼 비용이 드는 계산이며, 게으른 초기화를 하는 게 좋을 수도 있다. 

 

출처 https://yceffort.kr/2020/10/IIFE-on-use-state-of-react

 

 


회고

Next.js 공부를 시작했는데 생각보다 공부할 게 많은 거 같다. 그냥 CRA랑 폴더 구조부터 다르고 라우트 방식도 완전 다르기 때문에 만만하게 보면 안 되겠다. 최종프로젝트에도 적용해보고 싶기 때문에 열심히 공부해야지! 오늘은 Next.js 강의 1일 차 다 듣고 리액트 보충 Throttling and Debouncing 강의도 2강까지 들었다. 근데 역시 프로젝트하면서 바쁘게 움직이다가 하루종일 강의만 들으려니까 너무 지루하고 잠이 쏟아진다~~😴

 

 

댓글