next.js
는 서버 사이드 렌더링, 동적 라우팅 등을 아주 손쉽게 사용할 수 있도록 해주는 강력한 리액트 프레임워크이다.
아래와 같은 특징을 가지고 있다
기본적으로 SPA는 클라이언트 사이드 렌더링 방식으로 구현되고 있다.
리액트의 index.html은 아래와 같이 구성되었었다.
리액트는 우선 index.html을 던지고, 그 다음에 필요한 자바스크립트를 불러오면서(해당 스크립트가
실행되면서) 우리가 작성한 컴포넌트가 표시되는 방시긍로 작동한다.
자바스크립트를 실행하는 주체는 바로 사이트에 접속한 이용자
이다.
CSR로 구현된 페이지는 html만 보았을 때는 요소들이 비워져 있다. 그렇지만 사이트에 사용자가 접속해서, 자바스크립트가 실행되는 순간 요소들이 채워지기 시작하는 것이다.
따라서 요소들을 그리는 주체가 클라이언트라는 점에서 클라이언트 사이드 렌더링이라고 말하는 것이다.
그럼 이것만 써도 상관없나요? 😊
아뇨 그랬으면 이런거 안배우죠. 마냥 좋은것만은 아니다.
이런 단점들을 보안하기 위해서
말 그대로 요소들을 그리는 주체가 서버
가 되는 것이다.
사용자가 사이트에 접속하는 순간, 서버는 html에다가 요소들을 그리고, 요소들이 그려진 html 파일과 함계 필요한 자바스크립트 코드를 사용자에게 보낸다.
그렇게 되면 사용자가 받는 파일은 빈 파일이 아니라, 요소들이 채워져있는 html 파일이 된다.
요소들이 그려져있다고 해서 실제로 사용자와의 인터렉션(클릭) 이 가능한 것은 아니다
SSR의 특징은
꼭 그렇진 않다
단점
Next.js 의 핵심적인 기능때문이며, 이것은 바로
쓱
Static Generation
이란 특정 페이지의 HTML을 빌드할 때
미리 그려놓는 것을 말한다. (react로 만든 앱은 배포시에 항상 빌드하는 과정이 필요하다 -> yarn build
)
사용자가 사이트에 접속할 때마다 서버가 직접 HTML을 그리는 것이 아니라, 미리 그려놓은 채로 해당 HRML을 저장해놓고, 사용자가 사이트에 접속하면 저장해놓은 HTML을 공급하는 것이다.
바로 그렇다.
Next.js 는 컴포넌트가 따로 외부에서 데이터를 가져오지 않는 한, 항상, static generation 방식으로 사전 렌저링을 진행한다. (따로 설정을 해주면, 외부에서 데이터를 가져오더라도 static generation방식으로 렌더링이 가능하다)
물론 이럼에도 단점은 있다.
next.js 앱을 yarn dev 로 실행한 경우에는 SSG 로 구현해놓은 코드라도 SSR 과 동일한 방식으로 작동한다. (아직 미리 빌드하지 않았기 때문)
한번 해보자
yarn create next-app (앱 이름) --ts
명령어를 입력하고 나면 pages 폴더가 생성되어있는데, 여기에 폴더를 만들거나, 파일 이름을 적으면 해당 이름대로 자동으로 route가 설정이 된다.
yarn dev
를 통해서 서버를 실행했을때, 가장 처음 보에는 페이지 컴포넌트는 index.js
이다.
그런데 사실 _app.js
와 _document.js
를 거쳐서 우리 웹사이트에 표시가 되는 것이 index.js
인데, _app.js
는 서버 자체에 접근하면 제일 먼저 실행되는 컴포넌트이다.
yarn dev 명령어로 실행해보면 데모 페이지가 나타난다.
next.js는 pages 폴더 안에 파일 혹은 폴더를 만든느 것만으로 해당 파일 이름으로 route 가 구성된다.
뿐만 아니라 id번호 등에 맞추어 페이지를 보여줘야하는 경우의 동적 라우팅도 지원한다.
한번 만들어보자.
리액트 작업할 때 하던 대로 컴포넌트를 만들듯이 채워주면 된다. [id] 컴포넌트의 경우, 함수 이름을 그대로 사용하는 것이 아니라, PostDetail
등으로 바꿔줘야 한다.
post[id].jsx
import { useRouter } from 'next/router'
import React from 'react'
function PostDetail() {
const router = useRouter()
const { id } = router.query
return <div>PostDetail {id}</div>
}
export default PostDetail
이렇게 작성해놓고 post/1 로 이동해보면
react-router-dom 설치 안했는데도 이렇게 라우팅이 가능하다!
페이지간 이동 시에는 Link
태그를 활용한다.
index.js
의 main
태그 부분을 비우고, 아래처럼 Link 태그를 추가한다.
<Link href='/post'>post로 가고싶어?</Link>
이렇게 해서 해당 문구를 클릭하면 post 페이지로 이동한다!
여기서 post로 이동하면 post/index.tsx를 우선시한다.
'import React from 'react'
function index() {
return (
<div>
하이! post 페이지!
</div>
)
}
export default index
Link 태그는 Link 태그가 사용자에게 보이면 (viewport 안에 들어오면) 자동으로 해당 페이지를 미리 로드 (prefetch) 한다. (정적 생성 방식으로 이용하는 페이지만 미리 로드하고, 서버 사이드 렌더링을 이용하는 페이지의 경우 Link 태그를 눌렀을 때만 로드한다.)
확인해보고 싶으면, yarn build 후에 yarn start를 해서 프로덕션 모드에서 확인할수 있다. (yarn dev를 통해 실행한 경우에는 미리 로드하지않음)
만약에 쿼리 파라미터를 함께 전달하고 싶다면?
<Link href='/post?name=hello'>
name과 함께 post로 가기
</Link>
이렇게 해주면 post/index.jsx 를 router.query를 사용해서 쿼리를 가져올수 있다.
import { useRouter } from 'next/router'
import React from 'react'
function Post() {
const router = useRouter()
const { name } = router.query
return <div>Post {name}</div>
}
export default Post
만약 동적 라우팅이라면?
<Link href='/post/1?name=hello'>
name과 함께 1번 post로 가기
</Link>
이렇게 한다면 PostDetail에는
import { useRouter } from 'next/router'
import React from 'react'
function PostDetail() {
const router = useRouter()
const { id, name } = router.query
return (
<div>
PostDetail {id} {name}
</div>
)
}
export default PostDetail
요렇게 작성할수 있을 것이다.
… 을 활용해서 만든 깊이가 있는 route 에 접근한다면?
<Link href='/post/1/2/3'>
깊이있는 post로 가기
</Link>
[...params].jsx
import { useRouter } from 'next/router'
import React from 'react'
function PostParams() {
const router = useRouter()
console.log(router.query)
return <div>PostParams</div>
}
export default PostParams
파일명에 적었던 이름으로, 배열 형태의 값이 들어가는 것을 볼수 있다.
만약 Link태그가 아니라 버튼으로 이동하고 싶으면 router.push()
를 사용한다.
router.push(url)
import { useRouter } from 'next/router'
import React from 'react'
function Post() {
const router = useRouter()
const { name } = router.query
return <div>
<button onClick={() => router.push('/post/1')}>1번 글로가기</button>
</div>
}
export default Post
그런 경우에는 useEffet와 함께 router.isReady
를 사용하면 된다. router.isReady
가 true가 된다면, query부분이 채워졌으므로 이제 query에 접근해도 된다.
useEffect(() => {
if (router.isReady) console.log(router.query)
}, [router.isReady])
next.js 는 기본적으로 css, css modules, 인라인 css (css-in-js), styled-jsx 를 모두 지원한다.
만약 sass 를 적용하고 싶다면, 그때는 sass 모듈을 (개발용 디펜던시로) 설치해야 한다.
yarn add -D sass
이전에 사용했던 styled-components 도 사용가능하다. 사용하는 방법은 기존과 다르지 않다.
yarn add styled-components
post컴포넌트에 한번 적용해보자!
import { useRouter } from 'next/router'
import React, { useEffect } from 'react'
import styled from 'styled-components'
export const Container = styled.div`
display: flex;
flex-direction: column;
`
function Post() {
const router = useRouter()
return (
<Container>
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 1 } })}>1번 글로가기</button>
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 2 } })}>2번 글로가기</button>
</Container>
)
}
export default Post
이렇게 해놓고 실행하면?
???
이런 오류가 생기는 이유는 바로 최초에 우리한테 보여지는 페이지(서버가 공급한 페이지/ 정적 페이지) 와 실제로 자바스크립트 코드가 실행되면서 수정되는 페이지 간에 className이 일치하지 않기 때문이다
따라서 이걸 해결하려면 next.config.js
를 아래처럼 수정해줘야한다.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
compiler: {
styledComponents: true
}
}
module.exports = nextConfig
이렇게 하면, 최초 빌드할 때와 서버/클라이언트에서 렌더링할때의 className을 일치시켜주게 된다!
styled-components 관련 코드를 분리하고 싶다면, styled폴더에 js파일 형태로 분리해놓고 코드를 작성하면된다.
styles/post.js
import styled from "styled-components";
export const Container = styled.div`
display: flex;
flex-direction: column
`
pages/post/index.js
import { useRouter } from 'next/router'
import React, { useEffect } from 'react'
import * as S from '@/styles/post'
function Post() {
const router = useRouter()
return (
<S.Container>
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 1 } })}>1번 글로가기</button>
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 2 } })}>2번 글로가기</button>
</S.Container>
)
}
export default Post
잘 적용되었다.
기본 img 태그가 아니라, Image 태그가 별도로 존재하는 이유는 바로 성능 최적화
때문이다.
next.js 의 image태그는 아래와 같은 특징이 있다
기본적으로는 아래와 같이 쓴다.
import Image from 'next/image';
<Image
src="/images/profile.jpg" // 이미지 주소, 경로
height={144} // CLS 방지를 위해 사용됨
width={144} // CLS 방지를 위해 사용됨
alt="Your Name"
/>
아래의 이미지를 사용해서 태그를 만들어보자 이미지를 public 폴더에 넣어준다
next는 자동으로 public 폴더 내부에 있는 파일들을 도메인/파일명
을 통해서 접근할수 있도록 하고 있다. (Static File Serving)
그래서 src에는 rmsid /파일명
으로만 작성해주면 된다!
import { useRouter } from 'next/router'
import React, { useEffect } from 'react'
import * as S from '@/styles/post'
import Image from 'next/image'
function Post() {
const router = useRouter()
return (
<S.Container>
<Image src="/doggy.jpeg" alt="doggy" height={500} width={500} />
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 1 } })}>1번 글로가기</button>
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 2 } })}>2번 글로가기</button>
</S.Container>
)
}
export default Post
이미지를가져올 경우 height 와 width를 직접 명시해줘야 한다.
만약에 아래와 같이 import 를 통해서 가져올 경우, height와 width 는 해당 파일의 크기대로 맞춰진다.
import { useRouter } from 'next/router'
import React, { useEffect } from 'react'
import * as S from '@/styles/post'
import Image from 'next/image'
import dogImage from 'public/dog.jpeg'
function Post() {
const router = useRouter()
return (
<S.Container>
<Image src={dogImage} alt="dog" />
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 1 } })}>1번 글로가기</button>
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 2 } })}>2번 글로가기</button>
</S.Container>
)
}
export default Post
<Image src={dogImage} alt="dog" priority />
poistion:relative
및 display:block
속성을 가지고 있어야한다.<Image src={dogImage} alt="dog" fill />
<Image src={dogImage} alt="dog" fill sizes="(max-width: 768px) 33vw, (max-width: 1200px) 50vw, 100vw"/>
next.config.js
에 허용할 도메인을 추가해줘야한다!images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'via.placeholder.com', // 전부 허용하고자 할 경우에는 **
port: '',
pathname: '**', // 전부 허용하고자 할 경우에는 **
},
],
},
next 는 웹푼토 또한 빌드 시에 미리 받아놓을수 있따.
https://fonts.google.com/ 에 존재하는 폰트는 모두 사용할 수 있다.
앱 전체에 Black Han Sans를 적용해보자
import type { AppProps } from 'next/app'
import { Black_Han_Sans } from 'next/font/google'
const blackHanSans = Black_Han_Sans({
weight: '400',
preload: false, // 한글 폰트라면 필요하다(정확히는 한글 subset을 지원하지 않아서 필요)
})
export default function App({ Component, pageProps }: AppProps) {
return (
<main className={blackHanSans.className}>
<Component {...pageProps} />
</main>
)
}
이렇게 해주면 컴포넌트 전체의 폰트가 Black Han Sans로 적용된다.
페이지 컴포넌트에도 동일하게 사용할수 있기 때문에, 특정한 부분에만 폰트를 적용하고 싶다면 위와 같이 작성하고 jsx요소에다가 className을 할당해주면 된다.
그럼 만약 styled-component와 함께 사용하고 싶으면..?
https://hangeul.naver.com/font
여기로 들어가서 나눔 글꼴을 다운로드 받고 그중에서 나눔 바른펜 폰트를 사용하려고 한다.
styled component에서 font를 사용할때는 global style을 활용했었다. 따라서 globalstyle을 위한 파일을 만든다.
styles/globalStyle.js
import { createGlobalStyle } from 'styled-components'
export const GlobalStyle = createGlobalStyle`
@font-face {
font-family: Nanum Barun Pen;
src: url('/fonts/NanumBarunpenR.ttf');
}
body {
font-family: Nanum Barun Pen;
}
`
pages/_app.js
import { GlobalStyle } from "@/styles/globalStyle";
export default function App({ Component, pageProps }) {
return <><GlobalStyle/><Component {...pageProps} /></>
}
글꼴이 적용된 모습이다.
새로고침을 할때마다 글자가 깜빡거리는 현상이 있다. 이를 FOUT 현상이라고 하는데 (Flash of Unstyled Text) styled component가 새로고침할때마다 폰트를 불러와서 생기는 현상이다. 이를 해결하기 위해서는 해당 폰트를 불러오는 부분을 따로 css형태로 분리해야한다.
다시 말해서 폰트를 불러오는 부분은 styled component를 사용하지 말라는 뜻
이렇게 global.css 에 폰트 관련 코드를 추가하고
global.css
@font-face {
font-family: Nanum Barun Pen;
src: url('/fonts/NanumBarunpenR.ttf');
}
body {
font-family: Nanum Barun Pen;
}
app.js 에 작성했던 GlobalStyle 내용을 삭제하고 아래와 같이 작성한다.
_app.js
import '@/styles/globals.css'
import Head from 'next/head'
export default function App({ Component, pageProps }) {
return (
<>
<Head>
<link rel="preload" href="/fonts/NanumBarunpenR.ttf" as="font" crossOrigin="anonymous" type="font/ttf" />
</Head>
<Component {...pageProps} />
</>
)
}
이제 다시 보니까 이전에 적용한 폰트 화면은 제대로 적용된 것이 아니었던 것 같다..
폰트를 여러번 가져오는 일을 없애기 위해서 next.config.js
에 아래 header부분을 추가한다.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
compiler: {
styledComponents: true
},
headers: () => {
return [
{
source: '/fonts/:font*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
]
},
}
module.exports = nextConfig
이렇게 하면 Link 태그로 페이지를 이동하더라도, font를 다시 가져오지 않는다.
next.js 에서는 Head
태그를 통해서 각 페이지별 Head 태그를 설정해줄수 있다. 아래처럼 페이지 컴포넌트에다가 Head 태그를 작성하면, 브라우저에 글 있는곳
이라는 타이틀이 표시된다.
import { useRouter } from 'next/router'
import React, { useEffect } from 'react'
import * as S from '@/styles/post'
import Image from 'next/image'
import Head from 'next/head'
function Post() {
const router = useRouter()
return (
<>
<Head>
<title>글 있는 곳?!</title>
</Head>
<S.Container>
<Image src="/doggy.jpeg" alt="doggy" height={500} width={500} />
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 1 } })}>1번 글로가기</button>
<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 2 } })}>2번 글로가기</button>
</S.Container>
</>
)
}
export default Post
Script
태그를 사용하면, 외부 스크립트를 효율적으로 가져올수 있다.
아래처럼 작성하면 js를 불러오는 과정에도 next.js 만의 최적화 기법이 적용된다.
pages/post/index.tsx
import React from 'react'
import * as S from '@/styles/post'
import Script from 'next/script'
function Post() {
return (
<>
<Script src="https://example.com/script.js" />
<S.Container>
<p>폰트가 적용됐나요?</p>
</S.Container>
</>
)
}
export default Post
nav, footer 등 페이지 별 공통 요소를 작성하기 위한 Layout 컴포넌트를 하나 작성해보자
(컴포넌트에 불과하기 때문에, 이 컴포넌트 단독으로 정적 생성이 된다거나 서버 사이드렌더링이 되지는 않는다.)
components/Layout.jsx
import React from 'react'
function Layout({ children }) {
return (
<>
<nav>내브바</nav>
<main>{children}</main>
<footer>푸터</footer>
</>
)
}
export default Layout
_app.js
에 아래처럼 작성하면 된다.
import type { AppProps } from 'next/app'
import '@/styles/globals.css'
import Head from 'next/head'
import Layout from '@/components/Layout'
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Layout>
<Head>
<link rel="preload" href="/fonts/NanumBarunpenR.ttf" as="font" crossOrigin="anonymous" type="font/ttf" />
</Head>
<Component {...pageProps} />
</Layout>
</>
)
}
이제 어느 페이지로 가던 layout이 보이게 된다.
외부에서 데이터를 가져오는 사이트를 SSG 방식으로 구현해보자.
외부에서 데이터를 가져오지 않는 경우에는 next.js가 기본적으로 SSG 방식으로 HTML을 그려놓는다.
하지만 외부에서 데이터를 가져오는 경우, 따로 설정을 해줘야 빌드할때 해당 데이터를 가져와서 저장해 놓고 HTML을 그려두게 된다.
export async function getStaticProps() {
요청을 보내고 데이터 받아오기
return { props: { 데이터 } }
}
페이지 컴포넌트에서 해당 데이터를 props 로 받아오듯이 가져오면 된다.
function 페이지컴포넌트({ 데이터 }) {
return (
<div>
</div>
)
}
한번 해보자 우선 axios를 설치한다.
Post 컴포넌트내부에 api 호출을 통해 데이터를 받는 함수를 작성한다.
export async function getStaticProps() {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
const posts = response.data
return { props: { posts } }
}
그러면 Post 컴포넌트에서 {posts}
와 같은 형태로 해당 데이터를 가져와서 사용하면 된다.
import { useRouter } from 'next/router'
import React, { useEffect } from 'react'
import * as S from '@/styles/post'
import Image from 'next/image'
import Head from 'next/head'
import Script from 'next/script'
import Link from 'next/link'
export async function getStaticProps() {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
const posts = response.data
return { props: { posts } }
}
function Post({ posts }) {
const router = useRouter()
return (
<>
<Head>
<title>글 있는 곳?!</title>
</Head>
<Link href="/">홈으로 가기</Link>
{posts.map((post) => (
<div key={post.id}>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
))}
</>
)
}
export default Post
여기서 만약에 예를 들어서, post/[id].jsx
를 위한 정적 사이트를 생성한다고 생각해보자.
만약 1번 게시글을 미리 받아와서 정적 사이트를 생성한다면?
export async function getStaticProps() {
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts/1`)
const post = response.data
return { props: { post } }
}
그런데 사용자는 post/1
로도 접근할수 있고, post/2
로 접근할 수도 있다.
그렇다면 특정 게시글을 위한 정적 사이트 생성을 위해서는,
즉, getStaticPaths는 유저가 접근 가능한 동적 라우트 주소가 무엇이 있는지를 지정해주는 함수인 것이다.
만약에 post를 가져온다면, 아래처럼 전체 리스트를 가져오도록 요청보내고, id 부분만 추출해서 하나의 배열에다가 저장하면된다. 현재 posts는 100번 까지 있으므로 ,[{ params: { id: 1 } }, { params: { id: 2 } }, …. , { params: { id: 100 } }] 형태의 배열이 생성된다. id값은 문자열로 작성해줘야함
export async function getStaticPaths() {
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts`)
const posts = response.data
const paths = posts.map((post) => ({ params: { id: `${post.id}` } }))
return { paths, fallback: false }
}
fallback 같은 경우는 3가지 옵션이 있는데,
const router = useRouter()
if (router.isFallback) {
return
만약 getStaticPaths를 작성했다면, 이제 아래와 같이 getStaticProps를 작성하고 페이지 컴포넌트에서 데이터를 사용해주면 된다.
```jsx
// getStaticPaths 에서 리턴한 paths 내부 값을 인자 형태로 받아올 수 있음
export async function getStaticProps({ params }) {
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts/${params.id}`)
const post = response.data
return { props: { post } }
}
이제 빌드할때 1번 게시글부터 100번 게시글 모두에 요청을 보내고 데이터를 저장해두게 된다.
정적 사이트 생성 방식은 데이터를 미리 받아서 저장해놓기 때문에, 예전 데이터를 보여줄 가능성이 있다. 만약 지속적으로 데이터가 업데이트 되는 경우라면, 서버사이드 렌더링을 사용하되, 그 외의 경우는 SSG를 사용하는 것이 좋다. 왜냐고? SSG가 성능이 더 좋으니까
yarn dev 로 실행한 경우에는 SSG로 구현해놓은 코드라도 SSR과 동일한 방식으로 작동한다.(아직 빌드하지 않았으므로)
ISR(Incremental Static regeneration)란 해당 주기마다 다시 데이터를 가져와서 정적 사이트에 그려놓는 방식을 말한다.
사용하기 위해서는 getStaticProps에 revalidate
를 명시해주면 된다.
export async function getStaticProps() {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
const posts = response.data
return { props: { posts }, revalidate: 10 }
}
만약 위처름 revalidate 값을 10으로 해놓으면, 정적 사이트가 생성되고 10초동안 저장해놓은 데이터를 사용하게 된다. (정적 사이트가 생성되고 10초동안은 사이트에 접속한 누구든지 동일한 데이터를 보게된다.)
10초가 지난 뒤에 누군가가 다시 해당 사이트에 접근한다면, Next.js는 그제서야 해당 요청을 다시 보내고 업데이트된 데이터를 또 10초동안 저장해놓고 사용자들에게 보여주게 된다.
서버 사이드 렌더링은 getServerSideProps
함수를 사용하면 된다. 사용방식은 SSG와 동일함
함수 이름만 변경해주는 느낌으로 작성하면 된다!
import axios from 'axios'
import Link from 'next/link'
import React from 'react'
export async function getServerSideProps() {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
const posts = response.data
return { props: { posts } }
}
function Post({ posts }) {
return (
<div>
<Link href="/">홈으로 가기</Link>
{posts.map((post) => (
<div key={post.id}>
<Link href={`/post/${post.id}`}>
<h1>{post.title}</h1>
</Link>
</div>
))}
</div>
)
}
export default Post
홈으로 가기 버튼을 눌렀다가 다시 post 로 오면, 위와 같이 저장해놓은 데이터를 사용하는 것을 알수 있다. (서버사이드 렌더링/ 정적 사이트 생성은 html과 json을 활용해서 미리 데이터를 넣어놓는다!)
이제 사용자가 해당 페이지로 접근할 때마다, Next.js는 데이터를 가져오는 요청을 다시 보내고 해당 데이터를 저장한다면
getServerSidePaths도 있을까?
정말 업데이트가 잦은 경우가 아니라면, SSR 방식보다는 SSG방식에 revalidate 옵션을 명시하면서 사용하는 것을 추천한다.
getInitialProps라는 기능도 있지만, next가 업데이트 되면서 getServerSideProps가 사실상 해당 기능을 대체하게 되었다.