이번 포스팅에서는 next.js의 getInitialProps
에 대해서 알아보고자 한다.
쉽게 말해 getInitialProps
는 data를 fetching하는 방법 중 하나라고 생각하면 된다.
주로 CSR인 React에서는 axios나 fetch함수를 사용했었는데 next.js에서는 getInitialProps와 다른 기타 메서드들을 사용한다!
서버사이드 렌더링을 하는 next.js에서 컴포넌트는 각 페이지마다 사전에 불러와야할 데이터가 있다.(이하 data fetching)
react
, vue
같은 클라이언트 라이드 렌더링(CSR)의 경우는 useEffect
, created
함수를 이용해 data fetching을 한다.
서버사이드에서 실행하는 next에서는 getInitialProps
를 이용해 data fetching 작업을 한다!
next v9 이상에서는
getInitialProps
대신getStaticProps
,getStaticPaths
,getServerSideProps
을 사용하도록 가이드 하니 주의 ! -> 밑에서 추가로 설명할 예정입니당
next.js는 SSR을 기반으로 하긴 하지만 페이지가 로드된 이후에는 CSR(Client Side Rendering)을 이용하는 방식을 차용한다.
pages/
안에 폴더를 만들면 해당 라우팅의 페이지들은 서버측에서 먼저 로드해준다.react를 예시로 들면 react는 로직에 따라 컴포넌트가 마운트 되고 난 후 하는 경우가 많다.
이 과정을 서버에서 미리 처리하도록 도와주는 것이 바로 getInitialProps
data fetching을 서버에서 해주면 좋은 점
- 속도가 빨라진다. 브라우저에서의 연산을 서버와 함께 하면서 미리 데이터를 받아오고 브라우저는 렌더링만 할 수 있기 때문.
- 코드 상의 처리가 깔끔해지고 로직 파악이 쉽다. 렌더링하는 함수와 data fetching을 하는 함수가 분리되어 로직 파악이 쉽고 initial한 데이터가 들어오는 과정을 전제로 코드를 작성할 수 있다.
fetching할 mok data
{
"test": {
"title": "test post",
"content": "test content"
},
"second": {
"title": "second post",
"content": "second content"
}
}
알아보기 전에 동적 url이란?
→ 가변적으로 변하는 url에 대해 동적 url을 지원.[]
문법으로 동적 페이지를 생성하는 동적 url을 만들 수 있다!
동적 url example
import { useRouter } from "next/router";
export default () => {
const router = useRouter();
return (
<>
<h1>post</h1>
<p>postid: {router.query.id}</p>
</>
);
};
위처럼 작성하고 localhost:3000/123
으로 접속하면 postid가 123으로 나온다.
pages/[값].tsx
왼쪽 페이지 구조의 값은 router.query.값
과 동일!!
import { useEffect } from "react";
import { useRouter } from "next/router";
import posts from "../posts.json";
const Posts = () => {
const router = useRouter();
const post = posts[router.query.id];
return (
<>
<h1>{post.title}</h1>
<h1>{post.content}</h1>
</>
);
};
export default Posts;
위의 코드를 실행 후 localhost:3000/test
로 접속하면 post값이 없기 때문에 title이 없다는 에러가 발생한다.
SSR인 next.js에서는 getInitialProps를 이용해 데이터를 미리 받아오고, 렌더링 할 당시에는 이미 값이 있기 때문에 렌더링이 되는 방식으로 사용한다.
import { useEffect } from "react";
import { useRouter } from "next/router";
import posts from "../posts.json";
const Posts = (props: { post: { title: string; content: string } }) => {
const router = useRouter();
return (
<>
<h1>{props.post.title}</h1>
<h1>{props.post.content}</h1>
</>
);
};
Posts.getInitialProps = context => {
// context.query.id = 'test'
return {
post: posts[context.query.id]
};
};
export default Posts;
getInitialProps내부에는 context, component 등 여러 객체가 있으며 그 중 query.id에 접근하여 우리의 url인 'test'를 받아오고, 데이터에서 test 객체를 꺼내와 post에 담는다.
context 객체??
context 객체는 아래의 키를 포함한다.
- params : 동적 경로를 사용하는 페이지에 대한 정보
- preview : preview 모드 여부 (공식문서 참조)
- previewData : setPreviewData로 설정된 데이터
- req : HTTP 요청 정보
- res : HTTP 응답 객체
- query : 쿼리 문자열
이 이슈는 매우매우 중요하다.
페이지가 렌더링 될 때 next내부에서 거치는 순서는 _app
-> page component
이다.
만약 _app
에서 getInitialProps
를 정의했다면, 하위 컴포넌트에서는 실행되지 않는다!
하위 컴포넌트에서도 getInitialProps
값을 반영하려면 아래와 같이 _app.tsx
에 코드를 추가해야 한다.
app.tsx
import "./globals.css";
function MyApp({ Component, pageProps }) {
return <Component ponent {...pageProps} />;
}
MyApp.getInitialProps = async ({ Component, ctx }) => {
let pageProps = {};
// 하위 컴포넌트에 getInitialProps가 있다면 추가 (각 개별 컴포넌트에서 사용할 값 추가)
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
// _app에서 props 추가 (모든 컴포넌트에서 공통적으로 사용할 값 추가)
pageProps = { ...pageProps, posttt: { title: 11111, content: 3333 } };
return { pageProps };
};
export default MyApp;
위에서 처음에 설명했듯이 next.js v9 이상에서는 getStaticProps
, getStaticPaths
, getServerSideProps
을 사용하도록 가이드한다.
getStaticProps
는 빌드 시 데이터를 fetch하여 static페이지를 생성한다. 고정되는 값으로 빌드 이후 값 변경이 불가능하다.
언제 필요한가?
- 페이지에 필요한 데이터가 빌드 시에 사용 가능할 때
- 데이터를 headless CMS에서 가져올 때
- 모든 사용자에게 같은 데이터를 보여줄 때
- SEO를 위해서 속도 빠른 페이지가 필요할 때
- Node api(
path
,fs
등)을 사용해야 할 때- 변하지 않는 공개적인 캐시 데이터를 가져올 필요가 있을 때
export async function getStaticProps(context){
return{
props : {}, // page component의 props로 전달되는 객체
};
}
Example
function Blog({ posts }) {
return (
<ul>
{posts.map(post => (
<li>{post.title}</li>
))}
</ul>
);
}
export async function getStaticProps() {
// 외부 api endpoint로 호출하면 post 정보를 가져온다
const res = await fetch("https://.../posts");
const posts = await res.json();
// post 데이터가 담긴 props를 빌드 시간에 Blog 컴포넌트에 전달
return {
props: {
posts
}
};
}
export default Blog;
pages/--/[id].tsx
형태의 동적 라우팅 페이지 중 빌드 시에 static하게 생성할 페이지를 정한다.
이곳에 정의하지 않은 하위 경로는 접근해도 페이지가 뜨지X.
동적라우팅 되는 경우의 수 따져서 하위로 넣을 수도 있음.
언제 쓰는데?
- 페이지는 사전에 렌더링되어야 하고(SEO) 매우 빨라야 할 경우.
동적라우팅 + getStaticProps
를 원할 경우 사용!
페이지가 동적라우팅을 사용하고 있고 getStaticProps
를 쓰는 경우 getStaticPaths
를 통해 빌드 타임 때 정적으로 렌더링할 경로를 설정해야 한다.
//빌드될 때 실행
export async function getStaticPaths() {
return {
//빌드 타임 때 아래 정의한 /dyna/1, /dyna/2, ... /dyna/동적인값 경로만 pre렌더링.
paths: [
{ params: { dynamic: 1 } },
{ params: { dynmic: 2 } }
......
{ params: { dynmic: 동적인값 } }
],
// 만들어지지 않은 것도 추후 요청이 들어오면 만들어 줄 지 여부.
fallback: true,
}
}
위에서 path
는 빌드타임에 pre-rendering할 경로들이다
서버사이드 렌더링(SSR) 시 활용하는 메서드.
해당 메서드는 런타임 환경에서 페이지에 접근 시 서버 측에서 실행된다.
언제 필요한가?
- 우선 미리 렌더링 해야하는 경우에만 사용한다!
- 외부로부터 데이터를 요청해 페이지에 렌더링이 필요한 경우 사용
- 단 이 메서드는 페이지 컴포넌트에 접근할 때마다 서버에서 항상 실행되기 때문에 getStaticProps보다는 속도가 느리고 추가 구성 없이는 결과 데이터를 캐싱할 수 X
- 데이터를 미리 렌더링 할 필요가 없는 경우 클라이언트 측에서 데이터를 패칭하는 것이 더 효율적!!
export async function getServerSideProps(context){
return{
props: {},
}
}
Example
function Page({ data }) {
// 데이터 렌더링
}
// This gets called on every request
export const getServerSideProps: GetServerSideProps = async context => {
// 외부 api로 부터 받은 데이터 패칭
const res = await fetch(`https://.../data`);
const data = await res.json();
// page 컴포넌트에 props로 전달
return { props: { data } };
};
export default Page;
자세한 내용은 공식문서와 기타 자료들을 참고해주세요🙌🏻