CSR / SSR with Next.js

지윤·2023년 6월 26일

TIL

목록 보기
2/4
post-thumbnail

CSR(Client-side Rendering)

CSR이란?

  • CSR (client side rendering) 이란 말 그대로 사용자가 페이지를 그린다.(렌더링 한다.)
  • 사용자가 웹사이트에 접속 -> 서버에 페이지에 필요한 리소스(code, asset) request한다.
  • 예를들어 페이지가 로드 될 때 상품에 관한 정보를 백엔드 서버에 달라고 요청하면
    사용자는 전달받은 리소스를 js까지 모두 로드한 후 화면에 표현한다.
    => 사용자가 랜더링 부담
  • (html, css, js 등 페이지 랜더링에 필요한 리소스가 모두 로드 된 후 사용자가 화면을 확인 가능)

CSR의 장단점

장점

  • 필요한 자바스크립트를 사전에 미리 불러와 초기 랜더 이후의 속도는 빠르다
  • 특히 사전에 백엔드와 통신하는 부분이 없다면 SSR을 사용할 이유가 없다.

단점

  • 초기 페이지 로딩이 SSR보다 느리다
  • 사용자가 페이지를 랜더하는 동안 빈화면을 보게되므로 UX가 좋지 않다.
  • 사용자가에게 랜더링을 부담 -> 사용자의 하드웨어에 의존

그럼 SSR은 무엇일까?

SSR(Server Side Rendering)

SSR이란?

  • 서버에서 페이지를 그린다.(페이지를 보여준다.)
  • 사용자가 웹 사이트 접속하면 서버에 필요한 페이지 요청 -> 서버에서 그려진 페이지 전달
  • js가 로드 되기 전에 완성된 페이지를 사용자가 먼저 확인
    => 서버가 랜더링 부담
  • (페이지 랜더링에 필요한 최소한의 리소스를 서버에서 그린 후 전달, 사용자는 다른 부가적인 리소스가 적용되기 전에 화면을 확인 가능)

SSR의 장단점

장점

  • SEO(검색 엔진 최적화) => 웹 사이트를 검색엔진이 크롤링하여 사용자에게 제공, 해당 과정을 최적화하여 더 많은 사용자에게 웹 사이트를 노출
  • 빠른 첫페이지 로딩속도
  • 서버에서 랜더를 부담하기 때문에 사용자가 느끼는 부담이 덜하다.

단점

  • 서버의 부담(생산 비용 증가 -> 단 SSG로 완화 가능)
  • 무거운 페이지라면 오히려 초기 페이지로딩이 오래 걸려 UX를 해칠 가능성이 높음
  • CSR 보다 더 많은 생산비용(코드를 많이 쳐야함, 인력) 추가 러닝 커브

SSG(Static Site Generation)?
신세계아님

  • SSR의 단점 중 하나인 서버 부담을 완화하는 기술
  • 사전에 미리 생성된 정적 파일로 변환하여 제공하는 방식이다. 이를 통해 동적인 요청에 대한 서버의 부담을 줄일 수 있다.

동작방식

  • 웹을 빌드 시점에 데이터와 함께 렌더링하여 정적 HTML 파일을 생성 -> 이 파일은 웹 서버에 저장되어 클라이언트 요청에 바로 제공된다.
    따라서 동적인 요청에 대해 서버 측에서 렌더링하는 SSR과는 달리, 미리 생성된 정적 파일을 제공함으로써 서버의 부담을 줄일 수 있다.

무엇을 사용해야 할까?

  • 단순한 정적 콘텐츠가 많은 경우에는 SSG가 효과적일 수 있지만, 동적인 콘텐츠나 사용자 개인화가 필요한 경우에는 SSR이 더 적합할 수 있다.
  • 따라서 SSR과 SSG는 각각의 상황에 맞게 선택하여 사용하는 것이 좋다.

요약

SPA(Single Page Application)로 구성된 웹 앱에서 SSR(Server-side Rendering)이 필요한 이유

SPA(Single Page Application)?

  • 말 그대로 페이지가 하나인 애플리케이션(.html 파일이 1개)
  • 클라이언트 측에서 페이지 전환 없이 동적으로 콘텐츠를 로드하고 표시하는 web application 이다.
  • SPA는 초기 로딩 시에 모든 필요한 HTML, CSS, JavaScript 파일을 다운로드해야 하므로 초기 로딩 속도가 느릴 수 있다.
    -> 사용자는 애플리케이션이 완전히 로드되기 전까지 빈 화면을 보게 될 수 있다.
  • SPA는 동적으로 콘텐츠를 로드하기 때문에 검색 엔진 크롤러가 페이지의 내용을 이해하고 인덱싱하기 어려울 수 있다.
    -> SEO (검색 엔진 최적화)에 부정적인 영향을 미칠 수 있다.

SPA로 구성된 웹 앱에서 SSR이 필요한 이유

  • SSR은 서버에서 페이지의 초기 렌더링을 수행하여 완전한 HTML 페이지를 클라이언트에게 제공.
    -> 초기 로딩 속도를 개선하고, 검색 엔진 크롤러가 페이지를 더 잘 이해하고 인덱싱할 수 있도록 한다.
    -> SEO(검색 엔진 최적화)에 도움

레퍼런스 : SPA로 구성된 웹 앱에서 SSR이 필요한 이유?

Next.js 프로젝트에서 yarn start(npm run start) 스크립트를 실행했을 때 실행되는 코드

처음에 이 질문을 봤을때.. 음..? 무슨말이지? 했다. 일단 가이드대로 https://github.com/vercel/next.js/ 에 들어가서 이것저것 눌러봤다. 음.. 그래서 start할때 무슨 코드를 봐야하는 건지 전혀 모르겠다..!
그래서 일단 예전에 연습했던 next.js 프로젝트를 켰다!
그리고 일단 npm run start해보기!

npm run start가 안된다.


위와 같은 에러가 나왔다. 그러고 보니 나는 항상 npm run dev 명령어로 서버를 실행했던 것 같은데.. 여기서 요구하는건 npm run start..?
-> npm run start vs npm run dev 를 참고해서 차이점을 알았다.
음.. 그래서 npm run start를 하려면 어떻게 해야하는데..? ->npm run start하기
대충 보니 npm build를 먼저 해주라고 한다.

Followed by either next start, when you want to start the production server:

  • next start starts the application in production mode. The application should be compiled with next build first.

음.. 잘된다!

npm run start를 했을때 코드 찾기

아무튼 본론으로 돌아와서! 아까 npm run start를 했을때 나왔던 에러 내용을 다시 보자!

마지막줄 /node_modules/next/dist/cli/next-start.js이 보여서 github에 들어가서 packages -> next를 찾아 들어갔다.

오..! 이게 내가 찾던 것 같은 느낌..?

일단, 명령어(npm run start 등)는 package.json을 참고 해봐야 한다. 레퍼런스: package.json의 scripts

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },

next-start.ts에 있는 코드는 다음과 같다.

#!/usr/bin/env node

import arg from 'next/dist/compiled/arg/index.js'
import { startServer } from '../server/lib/start-server'
import { getPort, printAndExit } from '../server/lib/utils'
import isError from '../lib/is-error'
import { getProjectDir } from '../lib/get-project-dir'
import { CliCommand } from '../lib/commands'
import { resolve } from 'path'
import { PHASE_PRODUCTION_SERVER } from '../shared/lib/constants'
import loadConfig from '../server/config'

const nextStart: CliCommand = async (argv) => {
  const validArgs: arg.Spec = {
    // Types
    '--help': Boolean,
    '--port': Number,
    '--hostname': String,
    '--keepAliveTimeout': Number,

    // Aliases
    '-h': '--help',
    '-p': '--port',
    '-H': '--hostname',
  }
  let args: arg.Result<arg.Spec>
  try {
    args = arg(validArgs, { argv })
  } catch (error) {
    if (isError(error) && error.code === 'ARG_UNKNOWN_OPTION') {
      return printAndExit(error.message, 1)
    }
    throw error
  }
  if (args['--help']) {
    console.log(`
      Description
        Starts the application in production mode.
        The application should be compiled with \`next build\` first.

      Usage
        $ next start <dir> -p <port>

      <dir> represents the directory of the Next.js application.
      If no directory is provided, the current directory will be used.

      Options
        --port, -p          A port number on which to start the application
        --hostname, -H      Hostname on which to start the application (default: 0.0.0.0)
        --keepAliveTimeout  Max milliseconds to wait before closing inactive connections
        --help, -h          Displays this message
    `)
    process.exit(0)
  }

  const dir = getProjectDir(args._[0])
  const host = args['--hostname']
  const port = getPort(args)

  const keepAliveTimeoutArg: number | undefined = args['--keepAliveTimeout']
  if (
    typeof keepAliveTimeoutArg !== 'undefined' &&
    (Number.isNaN(keepAliveTimeoutArg) ||
      !Number.isFinite(keepAliveTimeoutArg) ||
      keepAliveTimeoutArg < 0)
  ) {
    printAndExit(
      `Invalid --keepAliveTimeout, expected a non negative number but received "${keepAliveTimeoutArg}"`,
      1
    )
  }

  const keepAliveTimeout = keepAliveTimeoutArg
    ? Math.ceil(keepAliveTimeoutArg)
    : undefined

  const config = await loadConfig(
    PHASE_PRODUCTION_SERVER,
    resolve(dir || '.'),
    undefined,
    undefined,
    true
  )

  await startServer({
    dir,
    isDev: false,
    hostname: host,
    port,
    keepAliveTimeout,
    useWorkers: !!config.experimental.appDir,
  })
}

export { nextStart }

여기서 npm run start의 코드를 관리하는 것 같다.(?)

중간에 있는 nextStart 함수를 보면 Types와 Aliases가 정의되어 있는데, 처음엔 명령어인가..? 하면서 명령어로 써보다가 안되길래 밑에있는 description을 읽었다.

const nextStart: CliCommand = async (argv) => {
  const validArgs: arg.Spec = {
    // Types
    '--help': Boolean,
    '--port': Number,
    '--hostname': String,
    '--keepAliveTimeout': Number,

    // Aliases
    '-h': '--help',
    '-p': '--port',
    '-H': '--hostname',
  }
  let args: arg.Result<arg.Spec>
  try {
    args = arg(validArgs, { argv })
  } catch (error) {
    if (isError(error) && error.code === 'ARG_UNKNOWN_OPTION') {
      return printAndExit(error.message, 1)
    }
    throw error
  }
  if (args['--help']) {
    console.log(`
      Description
        Starts the application in production mode.
        The application should be compiled with \`next build\` first.

      Usage
        $ next start <dir> -p <port>

      <dir> represents the directory of the Next.js application.
      If no directory is provided, the current directory will be used.

      Options
        --port, -p          A port number on which to start the application
        --hostname, -H      Hostname on which to start the application (default: 0.0.0.0)
        --keepAliveTimeout  Max milliseconds to wait before closing inactive connections
        --help, -h          Displays this message
    `)
    process.exit(0)
  }

Usage를 보니 next start <디렉토리(생략가능)> -p <port 넘버>로 port 넘버를 설정 할 수 있는 것 같다(!).
그리고 위에서 삽질했던 npm start! 여기 description에 아주 친절하게 아래와 같이 설명되어 있다(^^)

Starts the application in production mode.
The application should be compiled with \`next build\` first.

packages.json을 아래와 같이 바꾸고,

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start -p 3010",
    "lint": "next lint"
  },

npm run start를 하면, localhost/3010 서버에서 실행이 된다!

코드를 살펴보면 nextStart 함수를 정의하고, next start를 실행할 때 함수가 실행된다. getPort (포트번호 가져옴), loadConfig(함수를 호출하여 production server를 위한 Next.js 설정 로드), startServer (함수가 호출되어 production mode에서 서버 시작) 등의 코드가 작성되어 있다.

요약하면 next.js에서 npm run start 스크립트를 실행했을 때 package.json에 있는 scripts의 명령어를 읽고 -> next-start.ts 파일을 읽는데, next-start.ts에서는
명령줄 인수 처리(Command-line argument parsing)를 하고, 설정을 로드하며, production server를 시작한다.

어렵다..!

항상 작동하는대로 코드만 짜다가 이렇게 동작원리를 찾아보니까 뭔가 새로웠다. 이것 저것 더 건드려보면서 공부를 해봐야 겠다.

profile
방금 태어난 개발자

0개의 댓글