CSR / SSR

정진우·2022년 10월 6일
0
post-thumbnail

CSR이란?

  • Client Side Rendering(CSR)은 JavaScript를 사용하여 브라우저에서 직접 페이지를 렌더링하는 것을 의미한다. 모든 로직, 데이터 페칭, 템플릿 및 라우팅은 서버가 아닌 클라이언트에서 처리된다.

CSR의 장점

  • 변경된 부분과 관련된 데이터만 가져오므로 서버사이드 렌더링보다 빠르고,
    서버의 부담도 줄일 수 있다.
  • 페이지를 전환하는 과정에서 깜빡임 없이 부드럽게 이동할 수 있다.

CSR의 단점

  • 웹 크롤러가 페이지를 색인화하려고 하면 빈 페이지처럼 보이게 되므로 SEO에 불리하다.
  • 모든 js 파일을 다운받아야 하기 때문에 초기 로딩 시간의 더 오래 걸린다.



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

  • 검색엔진 최적화
  • 초기 페이지 로드가 빨라진다.



Next.js 프로젝트를 세팅한 뒤 yarn start 스크립트를 실행했을 때 실행되는 코드

  • next.js 프로젝트를 세팅한 뒤 yarn start 스크립트를 실행하면
    next.js/packages/next/cli 경로에 있는 next-start.ts 파일이
    실행되고 코드는 아래와 같다.

  • 코드를 이해하기 위해 import된 함수들의 로직을 주석으로 추가해보았다.

#!/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'
// getPort
// export function getPort(args: arg.Result<arg.Spec>): number {
// (args['--port']의 타입이 숫자인 경우 args['--port']를 리턴)
//  if (typeof args['--port'] === 'number') {
//    return args['--port']
//  }
//
//  (env 파일에 존재하는 PORT 환경 변수)
//  const parsed = process.env.PORT && parseInt(process.env.PORT, 10)
//  (parsed가 숫자인 경우 parsed 리턴)
//  if (typeof parsed === 'number' && !Number.isNaN(parsed)) {
//    return parsed
//   }
//   (위 두 조건에 맞지 않는 경우 3000 리턴)
//   return 3000
// }

// printAndExit
// export function printAndExit(message: string, code = 1) {
//   if (code === 0) {
//     console.log(message)
//   } else { 
//     console.error(message)
//   }
//   (code가 0인 경우 어떤 종류의 오류도 없이 프로세스를 종료한다는 의미)
//   (code가 1인 경우 일부 실패와 함께 프로세스를 종료한다는 의미)
//   process.exit(code)
// }

import * as Log from '../build/output/log'
import isError from '../lib/is-error'
// isError
// lib.es5.d.ts 파일에 정의된 Error를 extends하여 NextError 정의 
// export interface NextError extends Error {
//   type?: string
//   page?: string
//   code?: string | number
//   cancelled?: boolean
// }
// 
// lib.es5.d.ts 파일에 정의된 Error의 타입
// interface Error {
//   name: string;
//   message: string;
//   stack?: string;
//  }
//
// export default function isError(err: unknown): err is NextError {
//   return (
//     typeof err === 'object' && err !== null && 'name' in err && 'message' in err
//   )
// }

import { getProjectDir } from '../lib/get-project-dir'
import { cliCommand } from '../lib/commands'
// export type cliCommand = (argv?: string[]) => void

const nextStart: cliCommand = (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 {
  	// nextStart 함수 내부에서 선언한 변수 validArgs와 nextStart 함수의 매개변수
    // argv를 arg 함수의 인자로 넘기고 arg 함수가 리턴하는 값을 args에 재할당
    args = arg(validArgs, { argv })
  } catch (error) { // import한 isError와 printAndExit을 활용한 에러 처리
    if (isError(error) && error.code === 'ARG_UNKNOWN_OPTION') {
      return printAndExit(error.message, 1)
    }
    throw error
  }
  
  if (args['--help']) {
  // npx next start -h를 터미널에 입력하면 실행되는 코드
    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'] || '0.0.0.0'
  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

// 서버 실행
  startServer({
    dir,
    hostname: host,
    port,
    keepAliveTimeout,
  })
    .then(async (app) => {
      const appUrl = `http://${app.hostname}:${app.port}`
      Log.ready(`started server on ${host}:${app.port}, url: ${appUrl}`)
      await app.prepare()
    })
    .catch((err) => {
      console.error(err)
      process.exit(1)
    })
}

export { nextStart }

확실하게 이해하지는 못했지만 서버 실행에 필요한 값들을 모듈화되어 있는 함수들을 활용해서
선언해준 뒤 그 값들을 활용해 에러 처리를 한 후 서버를 실행하는 것으로 요약할 수 있을 것 같다.

profile
프론트엔드 개발자를 꿈꾸는

2개의 댓글

comment-user-thumbnail
2022년 12월 7일

진우님 화이팅

1개의 답글