Next.js - Pre-renders와 SEO

김명주·2023년 5월 1일
0

Pre-render

Next.js는 기본적으로 모든 페이지를 Pre-render 한다. Pre-render란, 말 그대로 미리 그려둔다는 뜻이다.
Pre-render의 주체는 서버다.

Pre-render가 있는 경우에는, 처음 클라이언트쪽에서 페이지를 로드할 때 이미 그려져있는 상태로 로드를 하게 된다. 이후에 JS 번들이 로드 되면, Hydration이라는 과정을 거쳐서 사용자랑 앱이 interaction을 할 수 있게 된다.
반대로 Pre-render가 없는 경우(SPA App인 리액트 등), 처음 로드할 땐 아무것도 없는 상태로 로드를 하게되고 JS가 로드되고 실행 되어야지만 그제서야 화면에 컴포넌트를 보여주게 된다.
즉 Pre-render란 JS를 제외한 기초적인 HTML UI가 이미 그려져서 로드되는 것을 의미한다고 볼 수 있다.

Hydration?

  • Hydration이란 Server Side 단에서 렌더링 된 정적 페이지와 번들링된 JS파일을 클라이언트에게 보낸 뒤, 클라이언트 단에서 HTML 코드와 React인 JS코드를 서로 매칭 시키는 과정을 말한다.

Pre-rendering과 SEO

만약 CSR(Client Side Render)만 제공한다면, Client(브라우저)처럼 동작하지 않는 검색엔진의 경우 아무런 데이터도 조회해 갈 수 없다. Pre-render를 해두면 Client처럼 동작하지 않는 검색엔진에게 필요한 데이터를 제공할 수 있고, 초기 로딩속도에 유리한 점이 있다.
SEO는 검색엔진 최적화로 다양한 검색엔진들이 내가 만든 사이트를 잘 읽어가서 상위에 노출시킬수 있도록 해주는 것인데, Pre-render가 아닌, JS를 해석할 수 없는 엔진에서는 아무것도 읽어갈 수 없다. 하지만 SSR(Server Side Render)을 해두면 JS를 해석할 수 없는 엔진이라도 이미 로드된 형태의 컨텐츠를 볼 수 있게 된다.
즉 SEO를 위해서는 Pre-render가 필요하고, SSR과 SSG(Static Side Generation)도 필요할 것이다.

Next.js의 Pre-render 방식

SSR & SSG

먼저 요약을 하면, SSG는 빌드 타임에 Pre-render를 하고, SSR은 요청 타임에 Pre-render를 한다.
Next.js는 SSG를 추천하는데 그 이유는 빌드 타임에 Pre-render를 진행하기 때문에 서버에 부하가 덜하기 때문이다.
하지만 SSR은 요청 타임에 Pre-render 하기 때문에, 사용자가 요청 시에 그제서야 node 서버가 화면을 미리 그려서 보여주기 때문에 요청 할 때마다 그려준다.

SSG의 2가지 상황

  1. page의 내용물이 외부 데이터에 의존적인 상황
    • getStaticProps만으로 해결 가능
  2. page의 paths 까지 외부 데이터에 의존적인 상황
    • getStaticPaths도 함께 활용해야 가능

Layouts

여러 페이지들의 공통 처리. 즉 페이지의 모듈화를 위해 Layout을 사용한다.

우선은 하나의 Layout만 사용하는 경우에는 페이지에 공통으로 들어있는 부분만 뽑아내서 Layout 컴포넌트를 생성한다.

import Head from 'next/head';
import styles from '../styles/Home.module.css';


// 페이지가 아닌 컴포넌트이기 때문에 SSR를 할 수 없다.


export default function Layout({children}) {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        {children}
      </main>

      <footer>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <img src="/vercel.svg" alt="Vercel" className={styles.logo} />
        </a>
      </footer>

  )   
}

그리고 _app.js 파일을 생성하여 모든 페이지를 품을 수 있는 구조를 만든다.

import Layout from "../components/Layout";

// 모든 페이지를 품을 수 있는 구조.
export default function App({Component, pageProps}) {
    return (
        <Layout>
            <Component {...pageProps}/>
        </Layout>
    )
}

그리고 기존 index.js에서 공통된 부분을 제거하면 내가 보여주고싶은 내용물만 신경쓰면 되는 구조가 된다.

import Head from 'next/head';
import styles from '../styles/Home.module.css';
import Link from 'next/link'

export async function getServerSideProps(){
  console.log("서버에서 데이터 보내는중..")
  return {
    props:{time: new Date().toISOString()}
  }
}



export default function Home({time}) {
  return (
      <>
        <h1 className={styles.title}>
        {time}
        </h1>
        <h1><Link href="/csr"><p>CSR</p></Link></h1>
        <h1><Link href="/ssg"><p>SSG</p></Link></h1>
        <h1><Link href="/isr"><p>ISR</p></Link></h1>
      </>
  )
}

그렇다면 여러개의 Layout을 사용하고 싶은 경우에는 어떻게 해야 할까?
공유할 하나의 SubLayout을 생성한다.

import Link from 'next/link'

export default function SubLayout({children}) {
  return (
    <div>
      <h1><Link href="/"><p>Home로</p></Link></h1>
      {children}
    </div>
  )
}

그리고 적용하고 싶은 파일에 getLayout함수를 선언한다.

import Layout from '../components/Layout';
import SubLayout from '../components/SubLayout';
import styles from '../styles/Home.module.css';
import { useEffect, useState } from 'react';




export default function CSR() {
    const [time, setTime] = useState();
    useEffect(() => {
        setTime(new Date().toISOString())
    },[])
  return (
      <>
        <h1 className={styles.title}>
        {time}
        </h1>
      </>
  )
}

// getLayout 선언
CSR.getLayout = function getLayout(page) {
  return(
    <Layout>
      <SubLayout>{page}</SubLayout>
    </Layout>
  )
}

그리고 이전에 만들어둔 _app.js 파일에 조건을 하나 둔다.

import Layout from "../components/Layout";

// 모든 페이지를 품을 수 있는 구조.
export default function App({Component, pageProps}) {
   const getLayout = Component.getLayout || ((page) => <Layout>{page}</Layout>)
   return getLayout(<Component>{...pageProps}</Component>)
}

위의 조건은 어떤 컴포넌트에 getLayout이 있다면 그걸 실행하고, 아니라면 Layout으로 감싸진 페이지를 리턴하는 함수를 실행하라는 조건이다.

profile
개발자를 향해 달리는 사람

0개의 댓글