indistreet을 이루는 기술

로토

인디 뮤지션들의 뮤지션 정보 및 공연 정보 등을
아카이빙 하는 사이트

기존에 있던 사이트에서 url만 이어받아서
2021년 2월부터 새로 만들기 시작했습니다.

Jamstack?

대충_잼이_쌓여있는_사진.png

Jamstack

  • JavaScript
  • APIs
  • Markup

그전에, 렌더링의 역사에 대해
살짝 알아봅시다.

태초에 Server Template이 있었으니

  • 정적인 HTML을 제공하는 게 아닌, Server Application에서
    여러가지 조건과 로직에 따라 HTML을 생성해서 내려주는 방식
  • HTML은 Server에서 내려주고 브라우저에서 렌더링하지만,
    인터랙션을 넣거나 하려면 JavaScript는 따로 Client에서
    실행해야합니다.
  • 기능 구현에 따라 같은 뷰 로직이 Server side에서도 구현되어야했고 Client에서도 구현이 되어야 했었습니다.
    • ajax를 통해 데이터를 불러와 Client에서 렌더링을 추가로 하는 경우 때문이죠.

Client Side Rendering

  • 브라우저 성능의 발전과 JS 스펙의 고도화로, 아예 렌더링을
    Client에서 다 처리해버리자는 움직임이 등장합니다.
  • API와 웹 애플리케이션이 완전히 분리되어 있고, 웹 애플리케이션은
    HTML, CSS, JS만 있는 형태
  • 배포는 빌드된 JS와 CSS, 그리고 HTML을 CDN에 배포하는 형식이
    ​주로 사용됩니다.
    • aws s3 + cloudfront
    • netlify
    • github pages
    • firebase hosting
    • vercel

Client Side Rendering

  • Server 상에서 실행하는 코드 없이, 브라우저에서 JS를 실행해서
    돌아가는 게 대부분이기 때문에 배포를 쉽고 빠르게 할 수 있는
    장점이 있습니다.
    • 이로 인해 별도의 서버 관리 없이 운영할 수 있습니다.
  • 애플리케이션에 트래픽이 폭발해도 Server 상에서 실행하는 코드 없이
    정적인 자원만 내려 주기 때문에 별도의 스케일 업-아웃이 필요 없습니다.
  • 별도의 Server가 없기 때문에 URL에 따른 처리도 전부 Client에서 하게
    ​됩니다.
    • 이른바 Single Page Application이 나타난거죠.

Client Side Rendering

  • SPA 형태로 만들어진 애플리케이션의 경우 어느 페이지를 접속해도
    매번 같은 index.html을 내려주기 때문에 meta:og 태그 관련
    ​문제가 발생합니다.
    • 서버를 통해 동적으로 생성하는 부분이 전혀 없어서 생기는
      문제입니다.
    • aws의 경우 lambda edge, firebase의 경우 function 이용해서
      어느정도 보완은 가능합니다.
      관련 내용: ODC를 구축한 기술, Firebase functions
    • 다만 이 경우에도 한계는 명확합니다.

Client Side Rendering

  • lighthouse 점수가 낮게 나오는 문제가 있습니다.
    • 초기 body가 거의 텅 비어있다가, JS가 돌면서 렌더링이 되는 방식이기 때문에 서버에서 미리 마크업을 만들어서 내려주는 형태보다는 페이지 로딩이 느릴 수 밖에 없습니다.

Server Side Rendering

  • node.js의 발전으로 Client와 Server를 같은 언어를 쓸 수 있게 되면서
    다시 렌더링의 책임을 Server로 일부 돌리는 움직임이 나타납니다.
  • 이는 기존의 Python, Java와 같은 언어에서 하던 방식과는 근본적으로
    다른 방식으로, Client, Server에 따라 각각의 언어로 뷰 로직을
    구현해야했던 과거의 방식과는 달리 거의 대부분의 뷰 로직을
    Client와 Server에서 공유합니다.

Server Side Rendering

  • 더 이상 serverless가 아니게 되므로 server 관리 책임이 생깁니다.
  • 관리해야하는 server가 있기 때문에 트래픽이 몰릴 경우
    스케일 업, 스케일 아웃 등의 관리가 필요할 수 있습니다.
  • docker 등으로 말아서 배포할 경우 빌드 및 배포 시간이 CSR 대비해서 더
    늘어납니다.
  • 세팅이 복잡합니다.

Static Site Generator

  • 동적인 페이지를 포함해서 사이트의 모든 페이지를
    미리 정적으로 만들어버리는 방법
  • 모든 동적인 케이스에 따라 사이트를 만들어버리므로,
    Server가 다시 필요없게 됩니다.
  • 또한 이미 생성된 정적인 페이지를 내려주므로 CDN을 통해
    제공할 경우 속도에서도 큰 이득을 얻을 수 있습니다.

Static Site Generator

  • 동적으로 렌더링해야하는 컨텐츠가 많을 경우
    빌드시간이 컨텐츠 갯수에 비례해서 증가합니다.
  • 배포된 이후에 컨텐츠의 내용이 바뀌면, 변경된 내용을
    반영하려면 다시 빌드하고 배포해야 합니다.

Incremental Site Regeneration

  • SSG의 단점을 보완하기 위한 방법으로, 정적 생성을 하는 점은 동일합니다.
  • 정적 생성 후 일정 캐시 타임 이후 같은 페이지를 방문했을 때, 그때 해당
    페이지만 빌드해서 배포하는 방식입니다.
  • 캐시타임 이후 빌드하는 부분 때문에 Server가 필요하긴 하지만, SSR 보다는 Server 의존도가 낮습니다.

다시, Jamstack으로 넘어와서

  • indistreet은 현재 next.js 기반으로 되어 있습니다.
  • next.js의 getStaticProps를 이용해 SSG와 ISR 중심으로
    사용하고 있습니다.
  • 각 페이지 당 2~3분 정도의 캐시타임을 적용해두었습니다.
export const getStaticPaths: GetStaticPaths = async ({ locales }) => {
  if (process.env.NEXT_PUBLIC_ENABLE_PREBUILD === 'Y') {
    const apolloClient = initializeApollo({})

    const result = await apolloClient.query<LiveListQuery>({
      query: LiveListDocument,
      variables: {
        start: 0,
        limit: 100,
        sort: 'startDate:DESC',
      },
    })

    const paths =
      flatten(
        result.data?.lives?.flatMap(live =>
          isNil(live) || isNil(locales)
            ? []
            : [
                locales.map(locale => ({
                  params: {
                    liveId: live.id,
                  },
                  locale,
                })),
              ]
        )
      ) ?? []

    return {
      paths,
      fallback: true,
    }
  }

  return {
    paths: [],
    fallback: true,
  }
}
export const getStaticProps: GetStaticProps<{ liveId: string }> = async ({ params }) => {
  const liveId = getParamsValue(params, 'liveId')
  if (isNil(liveId)) {
    return {
      notFound: true,
    }
  }

  const apolloClient = initializeApollo({})

  const res = await apolloClient.query<FindOneLiveQuery>({
    query: FindOneLiveDocument,
    variables: {
      id: liveId,
    },
  })

  if (!res.data.live) {
    return {
      notFound: true,
    }
  }

  return {
    props: {
      initialApolloState: apolloClient.cache.extract(),
      liveId,
    },
    revalidate: 60,
  }
}
  • vercel을 통해 배포하고 있습니다.
  • SSG 혹은 ISR 된 파일의 경우, vercel을 이용하면
    알아서 CDN 처리를 해줍니다.
  • 캐시 만료 후 다시 빌드가 필요할 때는 aws lambda 형태로
    돌아가기 때문에 Server 부담이 적습니다.
  • PR 별로 배포본이 따로 생기기 때문에 기능별 테스트를
    쉽게 할 수 있습니다.
  • headless cms 도구
    • 컨텐츠를 관리하는 어드민만 있으며, 컨텐츠 자체를 제공하는 페이지는
      없는 것을 headless cms라고 합니다
    • 워드프레스에서 어드민만 있는 형태라고 생각하면 이해가 편합니다.
  • 어드민 상태에서 클릭 몇번으로 모델을 생성하고, 필드를 추가하고 할 수 있습니다.
    • 이렇게 만들어진 모델은 REST 기반으로 CRUD API가 자동으로 생깁니다.
  • 기본으로 제공되는 어드민의 완성도가 높습니다.
  • graphql을 기본 플러그인으로 지원합니다.
  • 대부분의 페이지에서 graphql을 통해 페이지 렌더링에 필요한 데이터를
    명시하고 있습니다.
  • 기존 REST API에서는 상황에 따라 한 페이지를 렌더링 하기 위해 여러개의
    API 콜이 필요했지만, graphql 환경에서는 한번에 다 가져오므로 뷰 로직이
    더욱 더 단순해집니다.
  • Apollo Client를 통해 로컬에서 쿼리 결과를 캐싱합니다.
  • graphql-codegen을 통해 TypeScript의 타입, 쿼리 등을 자동으로 생성합니다.

Q&A

감사합니다.

indistreet 많이 써주세요!!

indistreet을 이루는 기술

By TaeHee Kim

indistreet을 이루는 기술

  • 497