CSR / SSR with Next.js

Lee Tae-Sung·2022년 10월 3일
0

Next.js

목록 보기
3/3
  • 강의 전 기존에 알고 있던 개념들을 정리해놓았고 추후 수정될 수 있습니다.
  1. CSR(Client-side Rendering)이란 무엇이며, 그것의 장단점에 대하여 설명해주세요.

CSR(Client-side Rendering)이란 웹 브라우저에 데이터들을 보여주는 마지막 작업인 컴포넌트들을 렌더링하는 작업을 하는 방법 중에 하나이다. 이미 렌더링에 필요한 로직들을 모두 Client(웹브라우저)에게 보내 Sever와 통신 없이도 Client의 요청에 따라 빠르게 렌더링 하는 방식을 말한다. 이 방식은 처음 모든 렌더링에 필요한 로직들을 보내 셋팅해야해야하기 때문에 초기에 로딩이 길어질 수 있다는 점과 SEO에 불리한 점이 단점입니다. 이와 상반되는 개념으로 SSR(Sever-side Rendering)이 있으며 장단점이 반대입니다.

  1. SPA(Single Page Application)로 구성된 웹 앱에서 SSR(Server-side Rendering)이 필요한 이유에 대하여 설명해주세요.

SPA는 기본적으로 CSR으로 구현되어 있습니다. 그래야 하나의 페이지에서 Client 요청에 따라 어플리케이션을 작동시킬 수 있습니다. 그러므로 CSR의 단점을 가집니다. 1. 첫 로딩이 느린점. 이는 고객에게 서비스를 소개도 하지 못한채 바로 이탈할 수 있다는 점에서 크리티컬한 단점입니다. 2. SEO(검색 엔진 최적화)의 불리함. 대부분의 고객들은 Search를 통해 서비스에 유입합니다. 이를 위해 SEO에 맞는 서비스를 개발하여 고객들이 검색을 통해 쉽게 우리 서비스에 접근할 수 있도록 해야합니다.

이러한 문제점들을 해결하기 위해 SPA에 SSR이 필요합니다.

  1. Next.js 프로젝트를 세팅한 뒤 yarn start 스크립트를 실행했을 때 실행되는 코드를 nextjs github 레포지토리에서 찾은 뒤, 해당 파일에 대한 간단한 설명을 첨부해주세요.
    https://nextjs.org/docs/getting-started (Next.js 세팅 가이드)
    https://github.com/vercel/next.js/ (Next.js Github 레포지토리)

=> 창피하게도 yarn start 시 실행 코드를 찾지 못했다 .....
=> 포기하다 시피 하다가 마지막으로 Next.js docs를 다시 한번 읽어봤다.

=> production server?... 엉? 이게 힌트가 되겠는데...?
=> server라는 키워드를 추적해보니

// 서버를 실행할 때 옵션, 서버 생성하는 인스턴스 모듈, 리퀘스트에 결과 값을 return 해주는 핸들러 모듈
import type { NextServerOptions, NextServer, RequestHandler } from '../next'
import { warn } from '../../build/output/log'
import http from 'http'
import next from '../next'

// 서버 생성시 옵션
interface StartServerOptions extends NextServerOptions {
  allowRetry?: boolean
  keepAliveTimeout?: number
}

export function startServer(opts: StartServerOptions) {
  let requestHandler: RequestHandler

  const server = http.createServer((req, res) => {
    return requestHandler(req, res)
  })

  if (opts.keepAliveTimeout) {
    server.keepAliveTimeout = opts.keepAliveTimeout
  }
	
  // 강제로 프로미스로 만드는 방법.
  return new Promise<NextServer>((resolve, reject) => {
    let port = opts.port
    let retryCount = 0

    server.on('error', (err: NodeJS.ErrnoException) => {
      if (
        port &&
        opts.allowRetry &&
        err.code === 'EADDRINUSE' &&
        retryCount < 10
      ) {
        warn(`Port ${port} is in use, trying ${port + 1} instead.`)
        port += 1
        retryCount += 1
        server.listen(port, opts.hostname)
      } else {
        reject(err)
      }
    })

    let upgradeHandler: any

    if (!opts.dev) {
      server.on('upgrade', (req, socket, upgrade) => {
        upgradeHandler(req, socket, upgrade)
      })
    }

    server.on('listening', () => {
      const addr = server.address()
      // 이렇게 변수들에 삼항 연산자를 적극적으로 활용하는군 ..
      const hostname =
        !opts.hostname || opts.hostname === '0.0.0.0'
          ? 'localhost'
          : opts.hostname

      const app = next({
        ...opts,
        hostname,
        customServer: false,
        httpServer: server,
        port: addr && typeof addr === 'object' ? addr.port : port,
      })

      requestHandler = app.getRequestHandler()
      upgradeHandler = app.getUpgradeHandler()
      resolve(app)
    })

    server.listen(port, opts.hostname)
  })
}

=> 상당히 Node.js 의 내용들이 많았다.
=> 나야 나름 Node.js를 학습해서 익숙했지
=> 프론트 개발한다고 하면 아예 모르는 사람들도 많지 않을까 ...

=> 코드를 리뷰하다보니 이 코드가 맞는듯 싶다.


강의 후에 추가적인 Next.js 공부

=> 모범 사례로 제시된 분의 블로그
https://jineecode.tistory.com/286

=> 내가 문제에 답을 찾으려 했다면 이 분은 이 질문이 Next.js의 이해와 연관되어 있을것이라고 생각하고 추적하신 것 같다.

=> 그래서 나도 create으로 생성되는 코드부터 공부를 다시 했다.

_app.js

import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

https://merrily-code.tistory.com/154

=> _app.js와 globals css, index.js 등에 대해 알 수 있었다.

=> 그리고 next.js github
=> 나도 처음에 cli(Command Line Interface)를 가장 먼저 뒤져보았지만 해당 코드를 이해할 수 없었다...

https://medium.com/@psychet_learn/cli-cli-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95-c8d000ebc162
=> cli에 대한 기본적인 설명

=> 아무튼 새벽 혼수 상태;;; 를 거쳐
스맨파 보다가 잠듦

next.js/packeages/next/cli/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 * as Log from '../build/output/log'
import isError from '../lib/is-error'
import { getProjectDir } from '../lib/get-project-dir'
import { cliCommand } from '../lib/commands'

// 이 해당 함수가 실행하면서 cliCommand 가 commands의 
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 {
    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'] || '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 }

=> cli 코드를 보면서 어디서 해당 명령어를 받아들이는지 이해하려 노력했다.
=> cliCommand 변수가 연결되는 아래 코드

next.js/packeages/next/cli/next-start.ts

export type cliCommand = (argv?: string[]) => void

export const commands: { [command: string]: () => Promise<cliCommand> } = {
  build: () => Promise.resolve(require('../cli/next-build').nextBuild),
  start: () => Promise.resolve(require('../cli/next-start').nextStart),
  export: () => Promise.resolve(require('../cli/next-export').nextExport),
  dev: () => Promise.resolve(require('../cli/next-dev').nextDev),
  lint: () => Promise.resolve(require('../cli/next-lint').nextLint),
  telemetry: () =>
    Promise.resolve(require('../cli/next-telemetry').nextTelemetry),
  info: () => Promise.resolve(require('../cli/next-info').nextInfo),
}

=> 이 코드를 보니 해당 코드가 각자 명령어들에 대해 각자 방향을 잡아주고 있는 것을 확인할 수 있었다. (index)

=> 그리고 여러 에러문등을 거쳐 startServer 가 샐행해 내가 위에 가장 먼저 언급한 해당 코드가 샐행된다.

profile
긍정적인 에너지를 가진 개발자, 이태성입니다.

0개의 댓글