기존 React앱을 next.js로 변경하는 작업을 하였고, 해당 작업을 하며 공부한 내용을 정리하는 글입니다.
- 기존 ‘src’ 폴더를 없애고, pages 폴더 안에 index.tsx, _app.tsx, _document.tsx 추가함
- Next.js에서 페이지 관련 자바스크립트 파일들은 전부 pages 폴더에 보관하면 되며, pages 폴더에 보관한 파일들은 파일명 그대로 route가 된다. (ex. /posts/index.tsx: “/posts”)
_app.tsx 코드 예시
import React from 'react';
import { RootStoreType } from '../interface';
import { RootProvider } from '../store/rootContext';
import { RootStore } from '../store/rootStore';
import { AppProps } from 'next/app';
import Layout from '../components/Layout';
import '../style.css';
const store: RootStoreType = new RootStore();
const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
return (
<>
<RootProvider value={store}>
<Layout>
<Component {...pageProps} />
</Layout>
</RootProvider>
</>
);
};
export default MyApp;
_document.tsx 예시
import Document, { Html, Head, Main, NextScript } from 'next/document';
class CustomDocument extends Document {
render() {
return (
<Html lang="ko">
<Head>
<meta charSet="utf-8" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Next.js CRUD" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default CustomDocument;
index.tsx 예시
import React from 'react';
import List from '../components/List';
import 'react-toastify/dist/ReactToastify.css';
const Home: React.FC = () => (
<>
<List />
</>
);
export default Home;
public 폴더
: 이미지 같은 asset 파일 보관pages 폴더
: 페이지 관련 js 파일 보관 (자동 라우팅)pages/_app.tsx
: 서버 요청시 가장 먼저 실행되는 파일. 모든 페이지에 적용할 공통적인 레이아웃 역할. (global style 적용하는 곳)pages/_document.tsx
: _app.tsx 파일 다음에 실행되는 파일. 기존 리액트앱의 index.html 역할을 대신하는 js 파일. 모든 페이지에 공통적으로 사용되는 <head>
나 <body>
내용 커스텀. <head>
안에 공통으로 들어가는 <meta>
태그를 지정해서 SEO를 설정해줄 수 있다.pages/index.tsx
: 기본 경로("/") 해당하는 페이지. 첫 화면을 담당하는 페이지임.후기 작성 폼 | 후기 상세 페이지 |
---|---|
후기 수정 | 후기 삭제 |
---|---|
to-do 리스트 | 카운터 페이지 |
---|---|
FAQ 페이지 | 404 페이지 |
---|---|
React는 라이브러리이고, Next.js는 리액트의 프레임워크이다.
라이브러리는 “어플리케이션을 만들 때 필요한 자원(기능: 함수)의 모임”이고, 프레임워크는 “코드를 작성하는 기본적인 틀을 제공해서 보다 효율적으로 어플리케이션을 만들 수 있도록 하는 소프트웨어 환경”이다. 즉, React에서 우리가 모든 것을 직접 생성하고 설정해 주었던 것들이 Next에서는 이미 만들어져 있다.
특히, SEO를 위한 SSR를 가능하게 해주는 프레임워크이다.
리액트는 대표적인 CSR(client side rendering)입니다. 이러한 리액트의 CSR적인 부분에서 SSR 적으로 바꿔 주기 위해서 next.js와 같은 라이브러리를 사용합니다.
Next.js는 SPA와 SSR의 단점을 해결하기 위해서 리액트에 서버사이드렌더링 기능을 더하여 SPA와 SSR의 장점을 가질 수 있게 된다.
* 이미지 출처: Performance differences in Next.js vs. Create React App - LogRocket Blog
사전 렌더링(pre-render)
이후에는 CSR 사용(axios, fetch, XMLHttpRequest)
하는 과정은 CSR
방식을 따른다.⭐️ ServerSide Cycle 구체적으로 보기
⭐️ ServerSide Cycle
- Next Server가 GET 요청을 받는다.
- 요청에 맞는 Page를 찾는다.
- _app.tsx의 getInitialProps가 있다면 실행한다.
- Page Component의 getInitialProps가 있다면 실행한다. pageProps들을 받아온다.
- document.tsx의 getInitialProps가 있다면 실행한다. pageProps들을 받아온다.
- 모든 props들을 구성하고, _app.js > page Component 순서로 rendering.
- 모든 Content를 구성하고 _document.js를 실행하여 html 형태로 출력한다.
이 흐름을 보았을 때, 모든 페이지에 공통적인 데이터 패칭이 필요하다면 _app.tsx에서 미리 데이터 패칭을 해주면 되고, 페이지마다 다른 데이터가 필요하다면 페이지마다 데이터 패칭을 해주면 된다.
2) Hot Code Reloading을 지원
react-dom-router
라이브러리를 사용하여 라우팅 설정을 해주어야 한다. 그로 인해 페이지의 경로에 대하여 직접 설정을 해줘야 한다.<Link />
컴포넌트를 사용해서 클라이언트 사이드 네이게이션을 사용해야함)pages/index.tsx
에 설정해주면 되고, head (메타 태그) 및 body 설정은 pages/_document.tsx
에, 공통 레이아웃은 pages/_app.tsx
에 설정해주면 된다.다만, pages 폴더 내에는 반드시 ‘페이지’와 관련된 코드만 넣어야 한다. (ex. 컴포넌트 불가)
/public
favicon.ico
/src
/components
/elements
/auth
AuthForm.tsx
AuthForm.test.ts
/[Name]
[Name].tsx
[Name].test.ts
/hooks
/types
/utils
/test
/api
authAPI.test.js
[name]API.test.js
/pages
index.test.js
/pages
/api
/authAPI
authAPI.js
/[name]API
[name]API.js
_app.tsx
_document.tsx
index.tsx
React (사전 렌더링 X) | Next.js (사전 렌더링 O) |
---|---|
Next.js 공식 도큐에서는 퍼포먼스 이유등으로 SSG 사용을 추천함
Next.js는 SSG(Static Generation), SSR(Server-side Rendering) 두 가지의 사전 렌더링 방식을 제공한다. 하이브리드 형태로 두 가지 사전 렌더링 방식을 모두 적용할 수 있으나, 공식 도큐에서는 SSG 사용을 추천한다.
SSG 작동 방식 | SSR 작동 방식 |
---|---|
SSG (Static Generation) | SSR (Server Side Rendering) | |
---|---|---|
작동 방식 | HTML을 빌드 타임에 각 페이지별로 생성하고 해당 페이지로 요청이 올 경우 이미 생성된 HTML 문서를 반환 | 요청이 올 때 마다 런타임에 해당하는 HTML 문서를 그때 그때 생성하여 반환 |
장점 |
|
|
사례 |
|
|
* 이미지 출처: SSR vs SSG in Next.js – tutorial for CTOs and devs
getInitialProps
: Next 9.3 버전 이전엔 getInitialProps
만으로 사전 렌더링 관련 문제를 전부 해결했지만, 9.3버전부터는 getInitialProps가 3가지로 분리됨getStaticProps
getStaticPath
getServerSideProps
getStaticProps | getStaticPath | getServerSideProps |
---|---|---|
|
|
|
사전 렌더링에서 데이터를 미리 fetch 하고 싶을 때, getStaticProps
async 함수를 export해서 사용할 수 있다.
getStaticProps
는 런타임이 아닌, 빌드 타임 (next build
) 에서만 실행이 되므로, 변동이 거의 없는 데이터 대상으로만 적용하는게 좋다.
next dev
) getStaticProps
가 항상 호출된다.You should use
getStaticProps
if:
- 페이지를 렌더링하는 데 필요한 데이터를 사용자의 요청 전, ‘빌드’할 때 사용할 수 있는 경우
- 데이터가 Headless CMS(콘텐츠를 생성/저장/관리하는 콘텐츠 관리 시스템만 제공하고 사용자들에게 콘텐츠가 보이는 부분은 API로 제공하는 시스템)에서 온 경우
- 페이지가 사전 렌더링 되고(SEO용) 매우 빨라야 하는 경우 - getStaticProps는 성능을 위해 CDN에서 캐시할 수 있는 HTML 및 JSON 파일을 생성함
- 데이터를 공개적으로 캐시할 수 있는지 여부. 단, 특정 상황에서 미들웨어를 사용하여 경로를 다시 작성하면 이 상태를 무시할 수 있음
function Blog({ posts }) {
// Render posts...
}
// This function gets called at build time
export async function getStaticProps() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
export default Blog
import { GetStaticProps } from 'next';
const FaqPage: React.FC<{ faqs: Faq[] }> = ({ faqs }) => {
...
}
export const getStaticProps: GetStaticProps = async () => {
const response: AxiosResponse = await API.getFaqs();
const faqs: Faq = response.data;
// Pass faq data to the page via props
return { props: { faqs } };
// ...
};
export default FaqPage;
특정 페이지의 경로(paths)가 외부 데이터에 의존할 때, getStaticPaths
를 통해 사전 렌더링을 실행할 수 있다.
faqs/1
라는 경로를 빌드 타임에 미리 사전 렌더링 할 수 있음You should use
getStaticPaths
if you’re statically pre-rendering pages that use dynamic routes and:
- 데이터가 데이터베이스에서 온 경우
- 데이터가 파일 시스템에서 온 경우
- 데이터를 공개적으로 캐시 할 수 있는 경우
- 페이지가 사전 렌더링 되고(SEO용) 매우 빨라야 하는 경우 - getStaticProps는 성능을 위해 CDN에서 캐시할 수 있는 HTML 및 JSON 파일을 생성함
마찬가지로, getStaticPaths
는 런타임이 아닌, 빌드 타임 (next build
) 에서만 실행이 되므로, 변동이 거의 없는 데이터 대상으로만 적용하는게 좋다.
next dev
) getStaticPaths
가 항상 호출된다.// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false }
}
import { GetStaticProps, GetStaticPaths } from 'next';
const PostShow: React.FC<{ post: Post }> = ({ post }) => {
...
}
export const getStaticPaths: GetStaticPaths = async () => {
const response = await axios({ url: `${BASE_URL}/post` });
const data = await response.data;
const paths = data.map(({ id }: Post) => ({
params: { id: String(id) }
}));
return { paths, fallback: false };
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const response: AxiosResponse = await axios({
url: `${BASE_URL}/post/${params.id}`
});
const post: Post = response.data;
// Pass post data to the page via props
return { props: { post } };
}
export default PostShow;
// console.log(paths)
[
{ params: { id: '1' } },
{ params: { id: '2' } },
{ params: { id: '3' } },
{ params: { id: '10' } },
{ params: { id: '11' } }
]
// console.log(params)
{ id: '11' }
getServerSideProps
는 getStaticProps
와 비슷하지만 서버 사이드 렌더링을 위한 함수이다.getStaticProps
처럼 컴포넌트에 props를 넘겨준다는 공통점이 있지만, 빌드 시가 아닌 매 request마다 실행된다는 차이점이 있다.You should use
getServerSideProps
only if:
- 매 요청 마다 데이터를 가져와서 페이지를 렌더링해야 하는 경우에만 getServerSideProps를 사용해야 한다. 데이터, 혹은 요청 속성(예: 인증 헤더 또는 geolocation) 때문인 경우도 있다.
- getServerSideProps를 사용하는 페이지는 요청 시 서버 측에서 렌더링 되며 cache-control 헤더가 구성된 경우에만 캐싱 된다.
- 요청 중에 데이터를 렌더링할 필요가 없는 경우 클라이언트 측에서 데이터를 가져오거나 StaticProps를 통해 가져오는 것을 고려해야 한다.
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
import { GetServerSideProps } from 'next';
const List: React.FC<{ posts: Post[] }> = ({ posts }) => {
...
}
export const getServerSideProps: GetServerSideProps = async () => {
const response: AxiosResponse = await API.getPosts();
const posts: Post = response.data;
// Pass post data to the page via props
return { props: { posts } };
};
export default List;
SEO: Search Engine Optimization의 약자로 웹에 올리는 컨텐츠가 구글, 네이버, 다음 같은 검색 엔진으로부터 제대로 인식이 될 수 있도록 최적화를 하는 작업
검색 엔진 최적화에는 여러가지 방법이 존재하나, 기본적으로 페이지에 메타 태그를 삽입해서 크롤러가 웹을 잘 분석하고 인덱싱 하는데 도움을 주는 방법이 있다.
Head
컴포넌트를 제공하기 때문에 쉽게 메타태그를 삽입할 수 있음next-seo
등 라이브러리를 사용할 수도 있음import Head from 'next/head';
const Meta = () => (
<>
<Head>
<title>Next.js 게시판</title>
<meta charSet="utf-8" />
<meta name="description" content="온보딩 3단계 Next.js로 게시판 만들기" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta property="og:title" content="Next.js 게시판" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://finda.co.kr/" />
<meta property="og:image" content="https://cdn.finda.co.kr/images/favicon/finda_192.png" />
<meta property="og:article:author" content="김하연" />
<link rel="icon" href="https://cdn.finda.co.kr/images/favicon/finda_192.png" />
</Head>
</>
);
export default Meta;
import Meta from '../layout/meta';
const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
return (
<>
<RootProvider value={store}>
<Layout>
{/* SEO 적용 */}
<Meta />
<Component {...pageProps} />
</Layout>
</RootProvider>
</>
);
};
export default MyApp;
- Next.js 공식 도큐
- NextJS 시작하기 - 2. React.js와 Next.js의 차이점 (framework vs. library, CSR vs. SSR)
- SSR(서버사이드 렌더링)과 CSR(클라이언트 사이드 렌더링)
- [Next.js] 기본 개념 : Next.js 란? Next.js를 왜 사용할까? Next.js의 장점은?
- [github] Prepare frontend interview - React
- Next.js의 Hydrate란?
- Keeping Server-Side Rendering Cool With React Hydration
- Next.js Hydrate를 최적화 시킬 수 있을까 (Partial Hydration)
- Next.js - 소개 및 특징
- Performance differences in Next.js vs. Create React App
- SSR vs SSG in Next.js – a practical overview for CTOs and devs
- Next.js 100% 활용하기 (feat. getInitialProps, getStaticPath, getStaticProps, getServerSideProps, storybook)
- CRA를 Next.js로 마이그레이션하기
- [FE] SSR(Server-Side-Rendering) 그리고 SSG(Static-Site-Generation) (feat. NEXT를 중심으로)
- [NEXT.js] SEO 처리하기 (feat. Head, meta 태그)