Next.js는 기본적으로 모든 페이지를 Pre-render 한다. Pre-render란, 말 그대로 미리 그려둔다는 뜻이다.
Pre-render의 주체는 서버다.
Pre-render가 있는 경우에는, 처음 클라이언트쪽에서 페이지를 로드할 때 이미 그려져있는 상태로 로드를 하게 된다. 이후에 JS 번들이 로드 되면, Hydration이라는 과정을 거쳐서 사용자랑 앱이 interaction을 할 수 있게 된다.
반대로 Pre-render가 없는 경우(SPA App인 리액트 등), 처음 로드할 땐 아무것도 없는 상태로 로드를 하게되고 JS가 로드되고 실행 되어야지만 그제서야 화면에 컴포넌트를 보여주게 된다.
즉 Pre-render란 JS를 제외한 기초적인 HTML UI가 이미 그려져서 로드되는 것을 의미한다고 볼 수 있다.
만약 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)도 필요할 것이다.
먼저 요약을 하면, SSG는 빌드 타임에 Pre-render를 하고, SSR은 요청 타임에 Pre-render를 한다.
Next.js는 SSG를 추천하는데 그 이유는 빌드 타임에 Pre-render를 진행하기 때문에 서버에 부하가 덜하기 때문이다.
하지만 SSR은 요청 타임에 Pre-render 하기 때문에, 사용자가 요청 시에 그제서야 node 서버가 화면을 미리 그려서 보여주기 때문에 요청 할 때마다 그려준다.
여러 페이지들의 공통 처리. 즉 페이지의 모듈화를 위해 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으로 감싸진 페이지를 리턴하는 함수를 실행하라는 조건이다.