CSR/SSR with Next.js
๐ฆ ์ก๋์ฌ๋
ํ๋์ ํค์๋๋ฅผ ์ก๊ณ ์ข ํธํ๊ฒ ์ ๋ฆฌํ๊ณ ์ถ์ด ๋ง๋ ์ก๋์ฌ๋
์ก๋์ฌ๋๋ ์กฐ์ ํ๊ธฐ ํ์
์์ ๋ณต
์ด ํธ์ฐฌํ์ก๋์ฐ์ด(้ๅๆฃ็ฐ)
์์ ์ ๋๋ ๋ง์ด๋ค.
์ก๋์ฐ์ด๋์ก๊ธฐ(้่จ)
์ ํํ๋ฅผ ๋น๋ ค์จ ์ฑ ์ผ๋ก ๊ตฌ์ฒด์ ์ธ ์ฒด๊ณ๊ฐ ์กํ์์ง ์์ ํ์์ด๋ค.
ํญ๋ชฉ์ด ๋ค์ ๋์กํ๊ณ ๋ด์ฉ์ ๊ตฌ๋ถ์ด ํผ๋๋์ด์๋ค๊ณ ํ๋ค. ๐คฃ
๋ค์ด๊ฐ๊ธฐ์ ์์
์ด ๊ธ์ ์ํฐ๋์์ ์ฃผ๊ดํ๋ ํ๋ฆฌ์จ๋ณด๋ฉ ํ๋ก ํธ์๋ ์ฑ๋ฆฐ์ง 7์ ์ง์ ๋ง์ ธ๋ณด๋ Next.js ํด๋ถํ ๊ต์ค - CSR / SSR with Next.js ์ ์ ์ถํ ์ฌ์ ๊ณผ์ ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ ๊ธ์ ๋๋ค.
๐๏ธ CSR(Client-side Rendering)์ด๋? ๊ทธ๋ฆฌ๊ณ ์ฅ๋จ์
์๋ฒ์์ ์ต์ด ํต์ ์์ HTML, CSS, JS๋ฅผ ์๋ต๋ฐ์์จ ํ JS์ ์ฝ๋์ ๋ฐ๋ผ ๋ฐ์ดํฐ๋ง ์์ฒญํ์ฌ ํด๋ผ์ด์ธํธ์ ๋ธ๋ผ์ฐ์ ์์ ํ๋ฉด์ ๋ ๋๋งํ๋ ๋ฐฉ์
์น ํ์ด์ง๊ฐ ๋ณต์กํด์ง์ ๋ฐ๋ผ ๋งค๋ฒ ํ์ด์ง๋ง๋ค ์๋ก์ด HTML์ ๋ฐ์์ค๋ ๋ฐฉ์์์ ๋ฒ์ด๋ ํ๋ฒ์ ๋ฐ์์จ ํ ๋์ํ๋ CSR ๋ฐฉ์์ด ๋ง์์ก๋ค.
์ฅ์
- ์ ์ ์์ ์ธํฐ๋ ์ ์ ๋ฐ๋ผ ํ์ํ ๋ฐ์ดํฐ๋ง ์์ฒญํ์ฌ ๋ฐ์์ค๊ธฐ ๋๋ฌธ์ ๋ถ๋ถ์ ์ผ๋ก ๋น ๋ฅด๊ฒ ์ธํฐ๋ ์ ํ ์ ์๋ค.
- ์๋ฒ์ ๋ถํ๋ฅผ ์ค์ผ ์ ์๋ค๋ ์๋ฏธ์ด๊ธฐ๋ ํ๋ค.
- ํ์ด์ง๋ฅผ ์ด๋ํ ๋๋ง๋ค ๋คํธ์ํฌ ํต์ ์ผ๋ก HTML์ ๋ฐ์์ค๋ ํํ๊ฐ ์๋๊ธฐ ๋๋ฌธ์ ๊น๋นก์์์ด ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๊ณ ๋ชจ๋ฐ์ผ ํ๊ฒฝ์์๋ ๋น ๋ฅธ ์๋๋ฅผ ์๋ํ๋ค.
Lazy loading
์ ์ง์ํ๋ค.
- Lazy loading: ํ์ด์ง ๋ก๋ฉ ์ ์ค์ํ์ง ์์ ๋ฆฌ์์ค๋ ๋์ค์ ๋ก๋ฉํ๋ ๊ธฐ์ ex) ์คํฌ๋กค์ ๋ด๋ ธ์ ๋, ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ๋ณด์ด๋ ๊ฒ
๋จ์
root
ํ๊ทธ๋ง ์๋ ๋น์ด์๋ HTML์ ๊ฐ์ ธ์์ JS๋ก ๋ ๋๋งํ๊ธฐ ๋๋ฌธ์, ํฌ๋กค๋ง๋ด์๊ฒ ํด๋น ์น์ ๋ํ ์ ๋ณด๋ฅผ ์ค ์๊ฐ ์๋ค. ์ฆ, SEO์ ๋ํด ์ต์ ํ ํ๊ธฐ๊ฐ ์ด๋ ต๋ค.
- ๋ค์ ๋งํด์, CSR๋ก ๊ตฌํ๋ ์น์ ๊ฒฝ์ฐ ๊ฒ์์ด ์ต์๋จ์ ๋ ธ์ถ๋๊ธฐ๊ฐ ์ด๋ ต๋ค๋ ์๋ฏธ์ด๋ค.
- ๋จ, ๊ตฌ๊ธ์ ๊ฒ์์์ง์ JS๊น์ง ์คํ์์ผ ํ๋จํ๋ ๊ฒ์์์ง์ด๊ธฐ ๋๋ฌธ์ ์์ธ์ด๋ค.
- ์ด๊ธฐ์ HTML, CSS, JS ์ฝ๋๋ฅผ ๋ชจ๋ ๋ฐ์์ค๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ์ปจํ ์ธ ๊ฐ ์ ์ ์๊ฒ ๋ ธ์ถ๋๋๋ฐ ์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค.
๐๏ธ SSR(Server-side Rendering)์ด๋? ๊ทธ๋ฆฌ๊ณ ์ฅ๋จ์
CSR๊ณผ ๋ฐ๋๋ก ์๋ฒ์์ ๋ ๋๋ง์ ์งํํ๋ค. ํด๋ผ์ด์ธํธ๊ฐ ํ์ด์ง๋ฅผ ์์ฒญํ๋ฉด ์๋ฒ์ธก์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์(์ฃผ๋ก DB) ํ์ด์ง๋ฅผ ๊ตฌ์ฑํ ํ ๋ธ๋ผ์ฐ์ ์๊ฒ ์ ๋ฌํ๋ค.
์ ์ ๊ฐ ํ์ด์ง๋ฅผ ์ด๋ํ ๋๋ง๋ค ์๋ฒ์๊ฒ ๋ค์ HTML, CSS, JS ๋ฑ์ ํ์ผ์ ์๋ต๋ฐ์์ ๋ณด์ฌ์ค๋ค.
์ฅ์
- ์ฌ์ฉ์๊ฐ ๋๋ผ๊ธฐ์ CSR๋ณด๋ค ๋น ๋ฅด๊ฒ ์ฒซ ํ์ด์ง๋ฅผ ๋ณผ ์ ์๋ค.
- SEO(๊ฒ์ ์์ง ์ต์ ํ)๊ฐ ์ข๋ค.
๋จ์
- ์๋ฒ์ ๋ถํ๊ฐ ์ฌํ ํธ์ด๋ค.
- ๊ฐ๋ฐ์๊ฐ ์ ๊ฒฝ์จ์ผํ ๋ถ๋ถ๋ค์ด 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 };