728x90
반응형

getServerSideProps

만약 getServerSideProps 라 불리는 async 함수를 export한다면 Next.js는 getServerSideProps에 의해 리턴된 data를 사용하면서 이 페이지를 매 요청시마다 pre-rendering 할 것이다.

 

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

 

context 파라미터는 다음 속성들을 포함하는 객체이다.

params 만약 동적 라우트를 사용하는 페이지라면 params는 라우트 파라미터들을 포함한다. 만약 페이지명이 [id].js라면 params{id : ...}와 같은 형태가 될 것이다.
req HTTP IncommingMessage object
res HTTP response object
query An object representing the query string
preview previewtrue라면 이 페이지는 preview모드, false면 아닌 것이다.
previewData setPreviewData에 의해 세팅된 preview data이다
resolvedUrl client transition을 위해 _next/data 접두어가 생략된 요청 url의 정규화 버전
locale active locale을 포함
locales 모든 지원 locales를 포함
defaultLocale 디폴트로 설정된 locale

 

getServerSideProps가 리턴하는 객체는 아래항목들이 포함한다.

  • props - 페이지 컴포넌트에 의해 리턴되는 props는 필수객체이다.
  • notFound - 페이지가 404 상태와 404 페이지로 리턴되도록 허용하는 옵셔널한 boolean 값이다.
  • redirect: 옵셔널한 값으로 내외부 자원으로 redirecting 해주는 값이다. 반드시 {destination: string, permanent: boolean} 형태여야 한다.

 

export async function getServerSideProps(context) {
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  if (!data) {
    return {
      redirect: {
        destination: '/',
        permanent: false,
      },
    }
  }

  return {
    props: {}, // will be passed to the page component as props
  }
}

 

 

언제 getServerSideProps를 써야할까?

getServerSideProps는 pre-render되는 페이지의 필요한 데이터가 요청시에 가져와야하는 경우에만 사용해라. 서버가 반드시 모든 요청에 대한 결과를 계산해야만 하기 때문에  getStaticProps보단 느리다.

 

만약 데이터가 pre-render 될 필요가 없다면, client-side에서 데이터를 가져오는 것을 고려해봐라.

 

  • 동적 라우트에서 정적인 데이터를 필요로 할 때 : getStaticProps
  • 동적 라우트에서 정적인 데이터를 필요로 하며, 정적인 페이지를 생성할 때 : getStaticPaths with getStaticProps
  • pre-render되는 페이지에 필요한 데이터를 요청시에 가져와야 하는경우 : getServerSideProps
  • pre-render가 필요없고 데이터를 가져와야하는 경우 : Axios

 

TypeScript : Use GerServerSideProps

타입스크립트를 사용할 때에는 next에서 GetServerSideProps를 사용할 수 있다.

 

import { GetServerSideProps } from 'next'

export const getServerSideProps: GetServerSideProps = async (context) => {
  // ...
}

 

 

Technical details

Only runs on server-side

getServerSideProps는 서버에서만 동작하고 브라우저에서 동작하지 않는다. 사용할 때

  • getServerSideProps는 요청시에만 실행된다.
  • 만약 이 페이지가 next/Linknext/router를 통해 요청된다면 Next.js는 API요청을 서버에 보낸다. 그리고 getServerSideProps를 실행한다. 그리고 getServerSideProps의 실행결과로 만들어진 JSON을 리턴한다. 

 

또한 getServerSIdePropspages에서만 요청된다.

 

 

SWR

리액트에서 쓰이는 useAsync같은 Hook이다. 클라이언트사이드에서 데이터를 가져올 때 이것이 매우 유용하다.

 

import useSWR from 'swr'

const fetcher = (url) => fetch(url).then((res) => res.json())

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

 

 

728x90
반응형
728x90
반응형

getStaticPaths(Static Generation)

만약 dynamic routes를 가지고 있는 페이지가 있고, 그 페이지가 getStaticProps를 사용한다면 그 페이지는 빌드시에 HTML pre-render될때 어떤 경로의 리스트의 페이지들인지 정의할 필요가 있다.

 

dynamic routes를 사용하는 어떤 페이지에서  getStaticProps라 불리는 async 함수를 export한다면, Next.js는 

getStaticProps에 의해 정의된 모든 paths들을 정적으로 pre-render 할 것이다.

 

export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } } // See the "paths" section below
    ],
    fallback: true or false // See the "fallback" section below
  };
}

 

 

The paths key(required)

path키는 pre-render되야하는 경로들을 정의한 것이다. pages/posts/[id].js 라는 이름의 dynamic routes를 사용하는 페이지가 있다고 가정해보자.  getStaticPaths를 해당 페이지에서 export하고 paths를 다음과 같이 리턴한다.

 

return {
  paths: [
    { params: { id: '1' } },
    { params: { id: '2' } }
  ],
  fallback: ...
}

 

그럼 Next.js는 posts/1, posts/2 를 빌드시에 정적으로 생성하고, pages/posts/[id].js의 페이지 컴포넌트로서 사용된다.

 

  • 만약 페이지 명이 pages/posts/[postId]/[commentId] 라면 params는 반드시 postId와 commentId를 포함해야한다.
  • 만약 pages/[...slug] 라고 한다면 params는 반드시 slug를 배열로 포함해야한다.

 

The fallbackkey (required)

getStaticPaths에 의해 리턴되는 객체는 반드시 boolean속성의 fallback 키를 포함해야한다.

 

fallback: false

 

만약 fallbackfalse이면 getStaticPaths에 의해 전달받지 못한 path들은 모두 404 페이지로 호출된다.

새 페이지가 자주 추가되지 않는다면 이방식은 유용하다. 만약 데이터 소스에 항목을 더 추가하고 새 페이지를 렌더링해야하는 경우엔 빌드를 다시 실행해야 한다.

 

아래 페이지를 예로 들어보자. getStaticPath에 의해 CMS로 부터 블로그 포스트리스트는 반환된다. 그리고 각 페이지들은 getStaticProps 에 의해 CMS로부터 각 포스트 데이터가 반환된다.

// pages/posts/[id].js

function Post({ post }) {
  // Render post...
}

// 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 }
}

// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  // Pass post data to the page via props
  return { props: { post } }
}

export default Post

 

만약 fallback이 true이면 getStaticProps의 행동이 변한다.

 

  • getStaticPaths로 부터 리턴된 경로들은 getStaticProps에 의해 빌드시에 HTML로 render된다.
  • 빌드시에 생성되지 않은 경로들은 404페이지로 로드되지 않는다. 대신 첫번째요청시에 "fallback"버전을 제공한다.
fallback: true는 next next 사용시 지원되지 않는다.

 

Fallback Pages: 로딩페이지 같은 역할

"fallback" 버전의 페이지는

  • 페이지의 props는 비워진다.
  • 라우터사용시 fallback이 렌더되면 router.isFallbacktrue값이 되는데 이로인해 fallback이 렌더링 되었다는 것을 확인할 수 있다.
// pages/posts/[id].js
import { useRouter } from 'next/router'

function Post({ post }) {
  const router = useRouter()

  // If the page is not yet generated, this will be displayed
  // initially until getStaticProps() finishes running
  if (router.isFallback) {
    return <div>Loading...</div>
  }

  // Render post...
}

// This function gets called at build time
export async function getStaticPaths() {
  return {
    // Only `/posts/1` and `/posts/2` are generated at build time
    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
    // Enable statically generating additional pages
    // For example: `/posts/3`
    fallback: true,
  }
}

// This also gets called at build time
export async function getStaticProps({ params }) {
  // params contains the post `id`.
  // If the route is like /posts/1, then params.id is 1
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  // Pass post data to the page via props
  return {
    props: { post },
    // Re-generate the post at most once per second
    // if a request comes in
    revalidate: 1,
  }
}

export default Post

 

 

When is fallback : true Useful?

fallback: true 는 app이 매우 많은 양의 정적페이지를 가지고 있을때 유용하다. 어떤 사람들이 아직 생성되지 않은 페이지들을 요청할 수 있다. 그때 그 유저는 로딩창을 볼 수 있을것이다. 다만 생성된 페이지들을 업데이트 하지 않는다.

업데이트하려면 ISR을 fallback true와 같이 쓰도록 하자.

 

fallback : 'blocking'

 

만약 fallback이 blocking이라면 getStaticPath에 의해 리턴되지 않은 새로운 페이지들은 HTML이 생성될 때 까지

기다릴 것이다. 원래는 데이터 받고 HTML이 생성되지만 그 반대로 작동한다.

  • getStaticPaths로 부터 반환된 경로들은 getStaticProps에 의해 빌드시 HTML로 render된다.
  • 빌드시에 생성되지 않은 경로들이 404페이지로 가지 않는다. 
  • loading/fallback 상태가 없고 그냥 요청된 페이지가 바로 로드된다.
fallback : 'blocking'은 next export 사용시 지원되지 않는다.

 

When should I use getStaticPaths?

동적 라우트를 사용해서 페이지를 정적으로  pre-rendering한다면 ( pages/[id].js ) 반드시 getStaticPaths를 써야한다.

 

 

TypeScript: Use GetStaticPaths

타입스크립트에서는 next에서 GetStaticPaths를 import 할 수 있다.

 

import { GetStaticPaths } from 'next'

export const getStaticPaths: GetStaticPaths = async () => {
  // ...
}

 

 

Technical details

  • getStaticProps를 동적 라우트 파라미터와 함께 사용한다면 getStaticPaths는 무조건 써야한다. getStaticPathsgetServerSideProps와 절대 같이 사용할 수 없다.
  • getStaticPaths는 서버사이드에서 빌드시에만 실행된다.
  • getStaticPathspage에서만 exported 될 수 있다. page가 아닌곳에선 export 할 수 없다.
  • 개발환경에서는 getStaticPaths는 매 요청시에 호출된다.
728x90
반응형
728x90
반응형

ISR(Incremental Static Regeneration)

ISR(Incremental Static Regeneration) 을 사용하면 트래픽이 들어올 때 백그라운드에서 다시 렌더링해서

기존 페이지를 업데이트 할 수 있게 해준다. 아래와같이 코드를 작성해보자.

 

about.js

function About({posts}) {
  console.log(posts)
    return (
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.name}</li>
        ))}
      </ul>
    )
  }
  
export default About

export async function getStaticProps(context) {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
    const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every second
    revalidate: 1, // In seconds
  }
}

 

실행해보자.

 

 

 

이제 블로그의 포스트리스트는 1초마다 재생성된다. 만약 새로운 블로그 포스트가 추가되면 앱을 재빌드하거나 재배포없이 거의 곧바로 활용가능해진다.

 

이것은 또한 fallback:true 옵션과 완벽히 동작한다.

 

Static content at scale

전통적인 SSR과는 다르게 ISR은 정적특징의 장점을 보장해준다.

  • 지연시간이 급증하지않고, 페이지는 지속적으로 빠르게 제공된다.
  • 페이지가 오프라인이 되지 않으며 만약 재생성이 실패해도 예전 페이지는 변경되지 않고 유지된다.
  • db나 백엔드에 로드 부하가 작다. 페이지들이 동시에 한번 재계산되기 때문이다.

 

파일 읽기 : process.cwd()

파일들을 getStaticProps 에서 직접 파일시스템을 통해 읽혀 질 수 있다. 그러기 위해선 반드시 파일의 full path를 가지고 있어야 한다.

 

Next.js는 코드를 몇개의 디렉토리 속으로 나누어 컴파일한다. 따라서 __dirname을 사용하면 실제 페이지 디렉토리 경로와 다른 경로를 리턴하기 떄문에 사용할 수 없다.

 

대신 우리는 process.cwd()를 사용할 수 있다. 이것은 Next.js 실행이 일어나는 디렉토리 정보를 준다.

 

about.js

import { promises as fs } from 'fs'
import path from 'path'

function About({posts}) {
  console.log(posts)
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <h3>{post.filename}</h3>
          <p>{post.content}</p>
        </li>
      ))}
    </ul>
  )
  }
  
export default About

export async function getStaticProps(context) {
  const postsDirectory = path.join(process.cwd(), 'posts')
  const filenames = await fs.readdir(postsDirectory)

  console.log(postsDirectory)
  console.log(filenames)

  const posts = filenames.map(async (filename) => {
    const filePath = path.join(postsDirectory, filename)
    const fileContents = await fs.readFile(filePath, 'utf8')

    // Generally you would parse/transform the contents
    // For example you can transform markdown to HTML here

    return {
      filename,
      content: fileContents,
    }
  })
  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts: await Promise.all(posts),
    },
  }
}

 

이렇게 만들고 posts폴더를 만든 후 abc.txt 파일을 생성해보자.

 

 

abc.txt

abc

 

실행 해보면 아래 화면과 로그를 확인할 수 있다.

 

 

posts 폴더 하위에 있는 파일들의 이름과 내용을 찾고 About 컴포넌트로 넘겨주었다.

 

 

Write server-side code directly

getStaticProps는 서버사이드에서만 동작한다. 클라이언트 사이드에서는 동작하지 않는다. 또한 브라우저에서 확인 가능한 js bundle에 조차 포함되지 않는다. 이것이 의미하는 것은 direct database queries를 직접 사용해서 정보를 가져올 수 있다는 것이다. getStaticProps로부터 API route를 fetch하면 안된다. 

 

getStaticProps(정적 생성)는 빌드 시 데이터를 가져오고 그곳에서만 처리됩니다. 즉, 프론트엔드와 백엔드가 혼합되지 않습니다. getStaticProps에 데이터가 필요한 경우 데이터를 가져오기 위해 /api에 대한 추가 네트워크 호출을 발생시키지 않아야 합니다. 대신 해당 논리와 함께 함수를 사용하거나 getStaticProps에서 직접 함수를 사용해야 합니다. 프론트엔드에 데이터가 필요한 경우 rest/graphql/whatever/api에서 데이터를 가져오거나 getServerSideProps를 통해 전달할 수 있습니다.

 

아래 예제를 통해서 js bundle에서 무엇이 제거되는지 확인할 수 있다( 브라우저에서 )

// This app shows what Next.js bundles for the client-side with its new SSG
// support. This editor supports TypeScript syntax.
import Cookies from 'cookies';
import Mysql from 'mysql';
import Link from 'next/link';
import SQL from 'sql-template-strings';
import Layout from '../components/Layout';

const pool = Mysql.createPool(process.env.DATABASE_URL);

export default function ({ projects }) {
  return (
    <Layout>
      <h1>Projects</h1>
      <ul>
        {projects.map((project) => (
          <li key={project.id}>
            <Link href="/projects/[id]" as={`/projects/${project.id}`}>
              <a>{project.name}</a>
            </Link>
          </li>
        ))}
      </ul>
    </Layout>
  );
}

export async function getServerSideProps({ req, res }) {
  const userId = new Cookies(req, res).get('user_id');
  const projects = await new Promise((resolve, reject) =>
    pool.query(
      SQL`SELECT id, name FROM projects WHERE user_id = ${userId};`,
      (err, results) => (err ? reject(err) : resolve(results))
    )
  );
  return { props: { projects } };
}
// This is the code that is bundled for the client-side:

import Link from 'next/link';
import Layout from '../components/Layout';
export var __N_SSP = true;
export default function ({ projects }) {
  return (
    <Layout>
      <h1>Projects</h1>
      <ul>
        {projects.map((project) => (
          <li key={project.id}>
            <Link href="/projects/[id]" as={`/projects/${project.id}`}>
              <a>{project.name}</a>
            </Link>
          </li>
        ))}
      </ul>
    </Layout>
  );
}

 

 

Statically Generates both HTML and JSON

getStaticProps를 가지고 있는 페이지가 빌드 시 pre-render 될 때, Next.js는 HTML파일 뿐 아니라 getStaticProps의 실행 결과인 JSON을 생성한다.

 

이것은 클라이언트에서 getStaticProps를 호출하는것이 아니라 단지 export된 JSON을 사용한다는 것을 의미한다.

 

 

Only allowed in a page

getStaticProps는 page에서만 exported 된다. 다른 곳에서는 사용할 수 없다. 이유는 리액트는 페이지가 render되기 전에 data 모두를 가지고 있어야 하기 때문이다. 

 

또한 반드시 export async function getStaticProps() {}로 동작해야한다.

TypeScript쓸 때는 export const getStaticProps: GetStaticProps = async (context) => {}

 

 

Run on every request in development

개발환경에서 getStaticProps는 매 요청시에 호출된다.

 

 

Preview Mode

요청시에 페이지가 render 되길 원하는 경우가 생긴다. 배포되기전에 프리뷰 초안을 보길 원할 수 있다.

자세한 내용은 Preview Mode 문서를 참조하자.

 

 

 

728x90
반응형
728x90
반응형

페이지 내부에서 SSR을 할 때 getStaticProps 를 사용하여 빌드 시 이 페이지를 미리 렌더링한다.

 

pages폴더 하위에 about.tsx 파일을 만들고 다음과 같이 작성해보자.

 

/pages/about.js

function About({data}) {
    console.log(data)
    return <div>About</div>
  }
  
  export default About

  export async function getStaticProps(context) {
    return {
      props: {
          data : "Teepo"
      }, // will be passed to the page component as props
    }
  }

 

아래 getStaticPropspropsdata를 담아서 About 컴포넌트로 보냈다.

 

npm run dev 명령어를 입력하고 확인해보자.

$ npm run dev

 

 

getStaticProps로 전달한 data값이 잘 넘어갔다.

 

나중에 fetch나 axios를 이용할 경우 다음과 같이 구현할 수 있다.

 

about.js

function About({data}) {
    console.log(data)
    return <div>About</div>
  }
  
export default About

export async function getStaticProps(context) {
    const res = await fetch(`https://jsonplaceholder.typicode.c/users`)
    const data = await res.json()

    return {
      props: { data }, // will be passed to the page component as props
    }
}

 

실행화면은 다음과 같다.

 

 

또한 redirect를 반환하여 리디렉션을 할 수 있다.

 

export async function getStaticProps(context) {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
    const data = await res.json()

    if (!data) {
      return {
        redirect: {
          destination: '/',
          permanent: false,
        },
      }
    }
  
    return {
      props: { data }, // will be passed to the page component as props
    }
}

 

다만, 빌드 시 리디렉션은 현재 허용되지 않으며 빌드 시 알려진 경우에 추가해야될 것들이 있다.

자세한 내용은 아래 주소로 확인해보자

 

https://nextjs.org/docs/api-reference/next.config.js/redirects

 

next.config.js: Redirects | Next.js

Add redirects to your Next.js app.

nextjs.org

 

 

언제 getStaticProps를 사용할까?

  • render되는 페이지가 필요한 데이터가 사용자 요청 전 빌드시에 활용가능한 data 일때
  • data가 headless CMS에서 오는 data일때
  • data가 사용자개인화가 필요하지 않은 공공성의 캐시된 data일때
  • 해당 페이지가 SEO를 위해 반드시 pre-render 된 페이지 여야하고 매우 빨리 로드되어야 할 때, -- getStaticProps는 HTML과 JSON파일을 생성하고 둘 다 CDN에 의해 캐시되서 성능적으로 매우 좋다.

 

TypeScript - getStaticProps

타입스크립트를 사용한 about 페이지는 다음과 같다.

 

about.tsx

import { GetStaticProps } from 'next'

interface data  {
    name : string
    age : number
}

function About(data : data) {
    console.log(data)
    return <div>About</div>
  }
  
export default About

export const getStaticProps: GetStaticProps = async (context) => {

    const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
    const data = await res.json()

    if (!data) {
      return {
        redirect: {
          destination: '/',
          permanent: false,
        },
      }
    }
  
    return {
      props: { 
          data: {
              name : "Teepo",
              age : 28
          }
        }, // will be passed to the page component as props
    }
}

GetStaticProps를 import 하고 data를 Object로 만들어보았다.

 

728x90
반응형

+ Recent posts