Next.js로 마이그레이션하기

허재원·2023년 2월 15일
0

Next.js란

Next.js는 React를 Server Side Rendering할 수 있게 해주는 프레임워크다. SSR의 장점은 첫 렌더링 화면이 빠르고 검색 엔진 최적화에 유리하므로 검색에 노출이 필요한 서비스에서 SSR은 필수적이라고 할 수 있다. 그리고 이후부터는 CSR 방식으로 동작하여 SSR의 장점과 CSR의 장점을 모두 갖고 있는 프레임워크라고 할 수 있다.

물론 React에서 SSR이 불가능한 것은 아니지만 러닝커브가 높기에 나의 경우 역시 위의 기능을 지원한 것과 더불어 Next.js로 마이그레이션하기로 결정했다. 그리고 이 외에도 Next.js에는 더 많은 기능을 지원하고 있기에 React로 프로젝트를 진행하던 중 Next.js로 마이그레이션하기로 결정했다.

Next.js로 마이그레이션하려는 이유

Code Splitting

React 사용하면서 lazy, Suspense를 통해 Code Splitting을 하는데 이 때 chunk 파일을 불러오기까지 로딩 컴포넌트를 보여준다.

Next.js는 Automatic Code Splitting을 제공한다.
페이지 이동을 할 땐 목적지 페이지에 필요한 chunk만 추가 로드하여 성능을 최적화한다.
또한 LinK 컴포넌트를 이용해서 Link 컴포넌트가 viewport와 교차하면 href로 연결된 페이지의 chunk 파일을 preload하여 앞서 말한 문제점에 대해 좀 더 나은 사용자 경험을 제공할 수 있다.

Image Optimization

앞서 작성했던 블로그 글에서 점진적 향상 기법으로 WebP 사용하기, 좋은 사용자 경험을 위해 CLS 해결하기를 작성했는데 Next.js는 이러한 문제를 바로 해결해준다.

Next/Image 컴포넌트에는 다음과 같은 기능이 있다.

  1. 자동 lazy loading을 통해 스크린 안에 있는 이미지만 로드하여 빠른 페이지 로드가 가능하다.
  2. srcSet을 통한 이미지 사이즈 최적화를 통해 디바이스 크기 별로 디바이스에 맞는 이미지를 다운로드할 수 있게 지원한다.
  3. placeholder를 통해 Layout Shift를 방지하여 CLS 최적화할 수 있다.
  4. WebP와 같은 용량이 작은 포맷으로 이미지를 변환하여 제공한다.

이러한 디바이스에 맞는 이미지 사이즈를 만들고, 용량이 작은 WebP 포맷으로 변환하는 등의 작업은 Next.js 백그라운드에서 이미지 최적화가 진행되어 좋은 사용자 경험을 제공할 수 있다.

SSR, SSG, ISR

SSR(Server Side Rendering)

SSR은 서버에서 페이지를 렌더링해서 클라이언트에 전달해주는 방식이다. 사용자가 해당 페이지를 요청할 때마다 HTML 페이지가 생성된다. 이러한 SSR을 사용하기 위해서는 매 요청마다 서버에 의해 호출되는 getServerSideProps 함수를 export해야 한다.

  • Next13에서는 const dynamicData = await fetch('https://...', { cache: 'no-store' });로 작성해야 한다.

SSG(Static Site Generation)

SSG는 Next.js에서 사용자가 요청하는 시점이 아닌 빌드 시에 페이지를 미리 생성하여 제공한다. Data Fetching이 필요한 경우에 getStaticProps라는 함수를 export 하여 함수 내에서 데이터를 받아서 리턴하면 빌드 시에 getStaticProps가 실행되고 리턴하는 값을 컴포넌트에서 받아서 페이지를 미리 렌더링하게 된다.
SSG는 빌드 시에 HTML을 만들고 요청 시 재사용하는데 SSR은 각각의 요청 시 HTML을 만들어 렌더링된다. 기본적으로 SSG가 빌드 타임에 만들어져 높은 성능을 가지기 때문에 SSG를 사용하는 것이 좋지만 상황에 따라 잘 구분해야 사용해야할 필요가 있다.

  • Next13에서는 const staticData = await fetch('https://...', { cache: 'force-cache' });로 작성해야 한다.

ISR(Incremental Site Regeneration)

ISR은 빌드 시점에 페이지를 렌더링한 후, 설정한 시간마다 페이지를 새로 렌더링한다. 즉, ISR로 구분했지만 SSG에 포함된 개념으로 revalidate값으로 설정하여 시간을 설정할 수 있다.

SEO

위의 개념과 이어지는 설명이다. Next.js는 기본적으로 Pre-render로 더 좋은 성능과 검색봇이 해당 페이지를 읽어갈 수 있어 SEO를 기대할 수 있다. 더불어 Next.js에는 Next/Head 컴포넌트를 지원한다. 여기에 title이나 image, description과 같은 og(open graph) 태그와 함께 사용하면 이 때 생성된 HTML을 통해 검색 엔진으로부터 인식이 될 수 있도록 최적화하는 작업을 할 수 있다.(React의 경우는 react-helmet 라이브러리 도움을 받을 수 있다.)

Next.js 마이그레이션

Updating package.json and dependencies

  • react와 react-dom은 두고, react-scripts 제거
    • 만약, react-router-dom을 사용하는 경우, react-router-dom도 같이 제거
  • npm i next
  • Next.js와 관련된 명령어 "scripts"에 추가
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

이제부터 npm run dev로 개발환경으로 돌리면 에러들을 열심히 발견할 수 있다ㅎㅎ

_app.tsx 설정

기존 App.tsx 파일

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
      useErrorBoundary: true,
    },
  },
});

function App() {
  return (
    <Provider store={store}>
      <ThemeProvider theme={theme}>
        <GlobalStyle />
        <QueryClientProvider client={queryClient}>
          <Router />
          <ReactQueryDevtools />
        </QueryClientProvider>
      </ThemeProvider>
    </Provider>
  );
}

export default App;

리팩토링

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Layout } from 'components';
import { AppProps } from 'next/app';
import Head from 'next/head';
import { Provider } from 'react-redux';
import { store } from 'store';
import { ThemeProvider } from 'styled-components';
import GlobalStyle from 'styles/globalStyle';
import theme from 'styles/theme';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
      useErrorBoundary: true,
    },
  },
});

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <Head>
        <title>HOW ABOUT OOTD</title>
      </Head>
      <Provider store={store}>
        <ThemeProvider theme={theme}>
          <GlobalStyle />
          <QueryClientProvider client={queryClient}>
            <Layout>
              <Component {...pageProps} />
            </Layout>
          </QueryClientProvider>
        </ThemeProvider>
      </Provider>
    </>
  );
}

_app.tsx는 Next.js에서 서버로 요청이 들어왔을 때 가장 먼저 실행되는 컴포넌트로, 페이지에 적용할 공통 레이아웃 역할을 한다.
_app.tsx 파일은 우리가 만든 모든 페이지가 초기화될 때 로딩되는 파일이기 때문에 전체 웹 애플리케이션에서 우리가 원하는 방식과 로직으로 페이지를 초기화할 수 있게 해준다. 즉, 초기화 로직을 컨트롤할 수 있게 도와주는 파일이다.

_app.tsx 파일의 대표적인 사용 예

  1. 각 페이지의 공통된 레이아웃 페이지 작성
  2. 전체 앱에 글로벌 CSS 적용

_document.tsx 설정

import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html>
      <Head>
        <meta charSet='utf-8' />
        <link rel='icon' href='/favicon.ico' />
        <meta name='theme-color' content='#000000' />
        <meta name='description' content='Web site created using create-react-app' />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}
  • _document 파일은 _app 다음에 실행된다.
  • _document 파일은 각 페이지가 초기화될 때 HTML 페이지 중 Document(<html>이나 <body> 등) 부분에 오버 라이딩을 제공한다. 그리고 CSS-in-JS의 서버 사이드 렌더링을 위한 설정을 할 때 사용한다.

해당 _document.tsx를 통해 불러온 HTML 확인

styled-components가 적용되지 않은 것을 확인할 수 있다.

CSS-in-JS 설정하기(styled-components)

SSR의 경우 자바스크립트 코드가 적용이 되지 않은 페이지가 미리 렌더링되기 때문에 CSS-in-JS로 스타일링을 하면 스타일이 적용되지 않은 HTML 마크업이 먼저 렌더링되는 문제가 발생한다.
Next.js에서는 이에 대한 해결책으로 HTML 파일에 CSS-in-JS 형식으로 작성된 스타일 요소들을 주입시켜 스타일이 뒤늦게 적용되는 문제를 해결할 수 있다.

_document.tsx 수정

import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html lang='ko'>
        <Head>
          <meta charSet='utf-8' />
          <link rel='icon' href='/favicon.ico' />
          <meta name='theme-color' content='#000000' />
          <meta name='description' content='how about ootd' />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

해당 _document.tsx를 통해 불러온 HTML 확인

styled-components가 적용된 HTML을 확인할 수 있다.

import { Link } from 'react-router-dom';
import Link from 'next/link';

Image 태그 변경

import Image from 'next/image

<img src={url} alt='' />
// width와 height props를 설정해주거나 fill props를 설정해줘야 한다.
<Image src={url} alt='' />

만약 위 코드처럼 react-router-dom에서 불러온 Link 태그를 사용했다면 아래처럼 바꿔주고, props도 to에서 href로 바꿔줘야 한다.

출처

0개의 댓글