๐Ÿ“ฆ ์žก๋™์‚ฌ๋‹ˆ

ํ•˜๋‚˜์˜ ํ‚ค์›Œ๋“œ๋ฅผ ์žก๊ณ  ์ข€ ํŽธํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๊ณ  ์‹ถ์–ด ๋งŒ๋“  ์žก๋™์‚ฌ๋‹ˆ

์žก๋™์‚ฌ๋‹ˆ๋Š” ์กฐ์„  ํ›„๊ธฐ ํ•™์ž ์•ˆ์ •๋ณต์ด ํŽธ์ฐฌํ•œ ์žก๋™์‚ฐ์ด(้›œๅŒๆ•ฃ็•ฐ)์—์„œ ์œ ๋ž˜๋œ ๋ง์ด๋‹ค.
์žก๋™์‚ฐ์ด๋Š” ์žก๊ธฐ(้›œ่จ˜)์˜ ํ˜•ํƒœ๋ฅผ ๋นŒ๋ ค์˜จ ์ฑ…์œผ๋กœ ๊ตฌ์ฒด์ ์ธ ์ฒด๊ณ„๊ฐ€ ์žกํ˜€์žˆ์ง€ ์•Š์€ ํ˜•์‹์ด๋‹ค.
ํ•ญ๋ชฉ์ด ๋‹ค์†Œ ๋‚œ์žกํ•˜๊ณ  ๋‚ด์šฉ์˜ ๊ตฌ๋ถ„์ด ํ˜ผ๋™๋˜์–ด์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. ๐Ÿคฃ

๋“ค์–ด๊ฐ€๊ธฐ์— ์•ž์„œ

์ด ๊ธ€์€ ์›ํ‹ฐ๋“œ์—์„œ ์ฃผ๊ด€ํ•˜๋Š” ํ”„๋ฆฌ์˜จ๋ณด๋”ฉ ํ”„๋ก ํŠธ์—”๋“œ ์ฑŒ๋ฆฐ์ง€ 7์›” ์ง์ ‘ ๋งŒ์ ธ๋ณด๋Š” Next.js ํ•ด๋ถ€ํ•™ ๊ต์‹ค - CSR / SSR with Next.js ์— ์ œ์ถœํ•  ์‚ฌ์ „๊ณผ์ œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

๐Ÿ—‚๏ธ CSR(Client-side Rendering)์ด๋ž€? ๊ทธ๋ฆฌ๊ณ  ์žฅ๋‹จ์ 

์„œ๋ฒ„์™€์˜ ์ตœ์ดˆ ํ†ต์‹ ์—์„œ HTML, CSS, JS๋ฅผ ์‘๋‹ต๋ฐ›์•„์˜จ ํ›„ JS์˜ ์ฝ”๋“œ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋งŒ ์š”์ฒญํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™”๋ฉด์„ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ์‹
์›น ํŽ˜์ด์ง€๊ฐ€ ๋ณต์žกํ•ด์ง์— ๋”ฐ๋ผ ๋งค๋ฒˆ ํŽ˜์ด์ง€๋งˆ๋‹ค ์ƒˆ๋กœ์šด HTML์„ ๋ฐ›์•„์˜ค๋Š” ๋ฐฉ์‹์—์„œ ๋ฒ—์–ด๋‚˜ ํ•œ๋ฒˆ์— ๋ฐ›์•„์˜จ ํ›„ ๋™์ž‘ํ•˜๋Š” CSR ๋ฐฉ์‹์ด ๋งŽ์•„์กŒ๋‹ค.

์žฅ์ 

  1. ์œ ์ €์™€์˜ ์ธํ„ฐ๋ ‰์…˜์— ๋”ฐ๋ผ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์š”์ฒญํ•˜์—ฌ ๋ฐ›์•„์˜ค๊ธฐ ๋•Œ๋ฌธ์— ๋ถ€๋ถ„์ ์œผ๋กœ ๋น ๋ฅด๊ฒŒ ์ธํ„ฐ๋ ‰์…˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์„œ๋ฒ„์˜ ๋ถ€ํ•˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ์ด๊ธฐ๋„ ํ•˜๋‹ค.
  1. ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์œผ๋กœ HTML์„ ๋ฐ›์•„์˜ค๋Š” ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊นœ๋นก์ž„์—†์ด ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๊ณ  ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์—์„œ๋„ ๋น ๋ฅธ ์†๋„๋ฅผ ์ž๋ž‘ํ•œ๋‹ค.
  2. Lazy loading์„ ์ง€์›ํ•œ๋‹ค.
  • Lazy loading: ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์‹œ ์ค‘์š”ํ•˜์ง€ ์•Š์€ ๋ฆฌ์†Œ์Šค๋Š” ๋‚˜์ค‘์— ๋กœ๋”ฉํ•˜๋Š” ๊ธฐ์ˆ  ex) ์Šคํฌ๋กค์„ ๋‚ด๋ ธ์„ ๋•Œ, ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด์ด๋Š” ๊ฒƒ

๋‹จ์ 

  1. root ํƒœ๊ทธ๋งŒ ์žˆ๋Š” ๋น„์–ด์žˆ๋Š” HTML์„ ๊ฐ€์ ธ์™€์„œ JS๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํฌ๋กค๋ง๋ด‡์—๊ฒŒ ํ•ด๋‹น ์›น์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ค„ ์ˆ˜๊ฐ€ ์—†๋‹ค. ์ฆ‰, SEO์— ๋Œ€ํ•ด ์ตœ์ ํ™” ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต๋‹ค.
  • ๋‹ค์‹œ ๋งํ•ด์„œ, CSR๋กœ ๊ตฌํ˜„๋œ ์›น์˜ ๊ฒฝ์šฐ ๊ฒ€์ƒ‰์–ด ์ตœ์ƒ๋‹จ์— ๋…ธ์ถœ๋˜๊ธฐ๊ฐ€ ์–ด๋ ต๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.
  • ๋‹จ, ๊ตฌ๊ธ€์˜ ๊ฒ€์ƒ‰์—”์ง„์€ JS๊นŒ์ง€ ์‹คํ–‰์‹œ์ผœ ํŒ๋‹จํ•˜๋Š” ๊ฒ€์ƒ‰์—”์ง„์ด๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์™ธ์ด๋‹ค.
  1. ์ดˆ๊ธฐ์— HTML, CSS, JS ์ฝ”๋“œ๋ฅผ ๋ชจ๋‘ ๋ฐ›์•„์˜ค๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ์ปจํ…์ธ ๊ฐ€ ์œ ์ €์—๊ฒŒ ๋…ธ์ถœ๋˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค.

๐Ÿ—‚๏ธ SSR(Server-side Rendering)์ด๋ž€? ๊ทธ๋ฆฌ๊ณ  ์žฅ๋‹จ์ 

CSR๊ณผ ๋ฐ˜๋Œ€๋กœ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง์„ ์ง„ํ–‰ํ•œ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋ฉด ์„œ๋ฒ„์ธก์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์„œ(์ฃผ๋กœ DB) ํŽ˜์ด์ง€๋ฅผ ๊ตฌ์„ฑํ•œ ํ›„ ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค.
์œ ์ €๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„์—๊ฒŒ ๋‹ค์‹œ HTML, CSS, JS ๋“ฑ์˜ ํŒŒ์ผ์„ ์‘๋‹ต๋ฐ›์•„์„œ ๋ณด์—ฌ์ค€๋‹ค.

์žฅ์ 

  1. ์‚ฌ์šฉ์ž๊ฐ€ ๋Š๋ผ๊ธฐ์— CSR๋ณด๋‹ค ๋น ๋ฅด๊ฒŒ ์ฒซ ํŽ˜์ด์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
  2. SEO(๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™”)๊ฐ€ ์ข‹๋‹ค.

๋‹จ์ 

  1. ์„œ๋ฒ„์˜ ๋ถ€ํ•˜๊ฐ€ ์‹ฌํ•œ ํŽธ์ด๋‹ค.
  2. ๊ฐœ๋ฐœ์ž๊ฐ€ ์‹ ๊ฒฝ์จ์•ผํ•  ๋ถ€๋ถ„๋“ค์ด CSR๋ณด๋‹ค ๋‹ค์†Œ ๋งŽ๋‹ค.

๐Ÿ—‚๏ธ SPA(Single Page Application)๋กœ ๊ตฌ์„ฑ๋œ ์›น ์•ฑ์—์„œ SSR(Server-side Rendering)์ด ํ•„์š”ํ•œ ์ด์œ ?

CSR๋งŒ์„ ์ด์šฉํ•˜์—ฌ SPA๋ฅผ ๊ตฌ์„ฑํ•˜๊ฒŒ ๋˜๋ฉด ์ฒซ ํ†ต์‹ ์—์„œ ๋ฒˆ๋“ค๋œ HTML, CSS, JS๋ฅผ ๋ฐ›์•„์™€์„œ ๋ Œ๋”๋งํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ์ปจํ…์ธ ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š”๋ฐ ๋‹ค์†Œ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๊ฒŒ ๋˜๋Š” ๋‹จ์ ์ด ์กด์žฌํ•œ๋‹ค.
๋˜ํ•œ HTML ํŒŒ์ผ์—๋Š” JS ์ฝ”๋“œ๊ฐ€ ์ง„์ž…ํ•˜๊ฒŒ ๋  entry root๋ฅผ ์ œ์™ธํ•˜๊ณค ์–ด๋–ค ๋‚ด์šฉ๋„ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™”๊ฐ€ ์ข‹์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.
์ด ๋•Œ, ์ดˆ๊ธฐ์— ํ•„์š”ํ•œ ๋ถ€๋ถ„๋“ค๋งŒ SSR์„ ํ†ตํ•ด ์œ ์ €์—๊ฒŒ ๋ณด์—ฌ์ฃผ๊ณ  ๊ทธ ๋’ค์—๋Š” CSR์ฒ˜๋Ÿผ ์œ ์ €์˜ ์ธํ„ฐ๋ ‰์…˜์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋˜๋ฉด ์œ„์˜ ๋‹จ์ ๋“ค์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

๐Ÿ—‚๏ธ Next.js ๊นŒ๋ณด๊ธฐ

Next.js ํ”„๋กœ์ ํŠธ์—์„œ npm run start๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด ์–ด๋–ค ์ฝ”๋“œ๋“ค์ด ์‹คํ–‰๋ ๊นŒ?

๋จผ์ € Next.js์˜ repo๋ฅผ ๊ฐ€๋ณด์ž. ๊ทธ๊ณณ์—์„œ ์ž˜ ์ฐพ์•„๋ณด๋ฉด packages/next/src/cli/next-start.ts๊ฐ€ ์žˆ๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, next๋กœ ์ž‘์„ฑ๋œ ํ”„๋กœ์ ํŠธ์—์„œ npm run start๋ฅผ ์ž…๋ ฅํ•˜๊ฒŒ ๋˜๋ฉด ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ด ํŒŒ์ผ์— ์ž‘์„ฑ๋˜์–ด์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

์ฝ”๋“œ๋ฅผ ๋ณด๊ธฐ ์ „์—, npm run start๋Š” ์–ด๋–ค ๋ช…๋ ฅ์–ด๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋ ๊นŒ?

์ด๋ฅผ ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด npx create-next-app@latest์„ ์‹คํ–‰ํ•˜์—ฌ ์ตœ์‹  ๋ฒ„์ „์˜ next ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  package.json์„ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์Šคํฌ๋ฆฝํŠธ์— ๋Œ€ํ•ด ์ •์˜ํ•˜๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

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

์ฆ‰, npm run start๋Š” next start๋ฅผ ์‹คํ–‰์‹œ์ผœ์ฃผ๋Š” ๊ฒƒ์ด๋‹ค. next start๋Š” production ๋ ˆ๋ฒจ์—์„œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋„์›Œ์ค€๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ๋ฆฌ์•กํŠธ์—์„œ npm run startํ•˜๊ฒŒ ๋˜๋ฉด ํฌํŠธ 3000๋ฒˆ์œผ๋กœ ์šฐ๋ฆฌ์˜ ์•ฑ์ด ๋„์›Œ์ง€๊ฒŒ ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ง์ด๋‹ค. ์ด ๋•Œ, ์ฃผ์˜ํ•  ์ ์€ ์ˆœ์ˆ˜ react์—์„œ์˜ npm run start๋Š” next์—์„œ์˜ npm run dev์™€ ๊ฐ™๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.(์ด ๋‘˜ ๋ชจ๋‘ development ๋ ˆ๋ฒจ์—์„œ ํฌํŠธ 3000๋ฒˆ์œผ๋กœ ์•ฑ์„ ๋„์šด๋‹ค.)

์ž, ๊ทธ๋Ÿผ ์ด์ œ ํ•œ ๋ฒˆ ์ฝ”๋“œ๋ฅผ ๋ด๋ณด์ž.

#!/usr/bin/env node

// jayden: ๋ญ”๊ฐ€ ๋งŽ์€ ๊ฒƒ๋“ค์„ import ์ค‘... ์ผ๋‹จ ๋ฌด์‹œํ•˜์ž
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) => {
  // jayden: next start ๋ช…๋ น์–ด ๋’ค์— ๋ถ™์ผ ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜์— ๋Œ€ํ•œ ํƒ€์ž… ์ง€์ •๊ฐ™๋‹ค.
  const validArgs: arg.Spec = {
    // Types
    '--help': Boolean,
    '--port': Number,
    '--hostname': String,
    '--keepAliveTimeout': Number,

    // Aliases
    '-h': '--help',
    '-p': '--port',
    '-H': '--hostname',
  };
  // jayden: ๋ช…๋ น์–ด ๋’ค์˜ ์˜ต์…˜์„ argv๋กœ ๋ฐ›๊ณ  ๊ทธ์— ๋Œ€ํ•œ ์–ด๋–ค ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ args๋กœ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.
  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;
  }
  // jayden: args ๊ฐ์ฒด์—์„œ key๊ฐ€ `--help`์ธ value๊ฐ€ ์žˆ๋‹ค๋ฉด ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
  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);

  // jayden: keepAliveTimeout ๊ฐ’์— ๋Œ€ํ•ด์„œ ์—๋Ÿฌ์ฒ˜๋ฆฌ
  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,
  );

  // jayden: ์œ„์˜ ์กฐ๊ฑด๋“ค์— ๋”ฐ๋ผ์„œ ์„œ๋ฒ„ ์‹œ์ž‘
  await startServer({
    dir,
    isDev: false,
    hostname: host,
    port,
    keepAliveTimeout,
    useWorkers: !!config.experimental.appDir,
  });
};

export { nextStart };

์ฐธ๊ณ