๐ก CSR(Client-Side Rendering)์ด๋ ๋ฌด์์ด๋ฉฐ, ๊ทธ๊ฒ์ ์ฅ๋จ์ ์ ๋ํ์ฌ ์ค๋ช ํด์ฃผ์ธ์.
CSR( ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ ๋๋ง )์ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํด ๋ธ๋ผ์ฐ์ ์์ ์ง์ ํ์ด์ง๋ฅผ ๋ ๋๋งํ๋ ๊ฒ์ ๋ปํฉ๋๋ค. ๋ชจ๋ ๋ก์ง, ๋ฐ์ดํฐ ํ์นญ, ๋ผ์ฐํ ์ ์๋ฒ๊ฐ ์๋ ํด๋ผ์ด์ธํธ์์ ์ฒ๋ฆฌ๋ฉ๋๋ค.
์ฅ์ ์ผ๋ก๋ ์ด๊ธฐ ๋ก๋ฉ ์ดํ์๋ ๋น ๋ฅธ ์๋๋ฅผ ๋ณด์ฌ์ค๋๋ค. ์ด๊ธฐ ๋ก๋๊ฐ ๋ฌ์ฑ๋๊ณ ๋ธ๋ผ์ฐ์ ์์ ์บ์ ๋ฐ ๊ตฌ๋ฌธ ๋ถ์ํ ์ ์๋ ํญ๋ชฉ์ด ์๋ฃ๋๋ฉด ์ดํ ํ์ด์ง ๋ก๋๋ ๋งค๋ฒ ์๋ฒ์์ ๋ชจ๋ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ผ ํ๋ SSR๋ณด๋ค ๋น ๋ฅด๊ณ ์ํํฉ๋๋ค.
๋๋ฒ์งธ๋ก๋ SSR์ ๋นํด ์๋ฒ์ ๋ถ๋ด์ด ์ ์ต๋๋ค. ์ด๊ธฐ ๋ ๋๋ง ์ดํ ํ์ด์ง๋ฅผ ์๋ก ๊ณ ์น๊ฑฐ๋ ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ํ์ด์ง๋ก ํด๋ฆญํ์ ๋ ๋ธ๋ผ์ฐ์ ๋ ์ ์ฒด ํ์ด์ง๊ฐ ์๋๋ผ ๋์ ์ผ๋ก ํ์ํ ์์๋ง ๋ ๋๋งํฉ๋๋ค. ์ด๋ ๋ผ์ฐํ ์ ๋งค๋ฒ ์๋ก์ด ํ์ด์ง๋ฅผ ๋ ๋๋งํด ๋ฐํํด์ผํ๋ SSR์ ๋นํด ์๋ฒ์ ๊ฐํด์ง๋ ๋ถ๋ด์ ์ค์ผ ์ ์์ต๋๋ค.
๋จ์ ์ผ๋ก๋ ์ฒซ์งธ๋ก ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ๋ค์ด๋ก๋ํ๊ณ ๋ถ์ํ๋ ๊ณผ์ ์์ ์ด๊ธฐ ํ์ด์ง ๋ก๋ฉ์ ์๊ฐ์ด ๋ค์ ๊ฑธ๋ฆด ์ ์๋ค๋ ์ ์ ๋๋ค. ์๋ฒ์์ ๋ ๋๋งํ ํ์ด์ง๋ฅผ ์ ๋ฌํด์ฃผ๋ SSR๊ณผ ๋ฌ๋ฆฌ ๋น HTML์์ ๋ชจ๋ ๋ก์ง์ด ๋ด๊ฒจ์๋ JS๋ฅผ ๋ฐ์ ๊ตฌ๋ฌธ์ ๋ถ์ํ๋ฉฐ ๋ ๋๋ง์ ์งํํ๊ธฐ์ ์ฒซ ์ง์ ์ ๋ก๋ฉ ์๋(FP)๊ฐ ๊ธธ๋ค๋ ๋จ์ ์ด ์์ต๋๋ค.
๋๋ฒ์งธ ๋จ์ ์ SEO์ ๋ค์ ์ทจ์ฝํ๋ค๋ ์ ์ ๋๋ค. ํ๋์ html, ๊ทธ๊ฒ๋ ๋น html์ ๊ฐ์ ธ์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ตฌ๋ฌธ ๋ถ์์ ํตํด ํ์ด์ง๊ฐ ๋ ๋๋ง๋๋ CSR์ SEO๋ฅผ ๋ด๋นํ๋ ํฌ๋กค๋ฌ ์ ์ฅ์์ ํด๋น ์๋น์ค์ ์ฌ๋ฌ ํ์ด์ง์์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋๋ฐ ๋ถ๋ฆฌํ ์ ์์ผ๋ฉฐ, ์ด๋ ๊ฒ์ ์์ง ์ต์ ํ์๋ ์ข์ง ์์ ์ํฅ์ ๋ฏธ์น ์ ์์ต๋๋ค. ๋ฌผ๋ก ์ต๊ทผ ๊ตฌ๊ธ ๋ด ๋ฑ ํฌ๋กค๋ฌ๋ค์ CSR์์๋ SEO๋ฅผ ๊ตฌ๋ถํ ์ ์๋ค๊ณ ํ์ง๋ง, ๊ทธ๋๋ ์ ํธ๋ ์ธก๋ฉด์์๋ MPA( Multi Page Application )๊ฐ ๋ ์ฐ์ธํ๋ค๊ณ ํฉ๋๋ค.
๐ก SPA(Single Page Application)๋ก ๊ตฌ์ฑ๋ ์น ์ฑ์์ SSR(Server-side Rendering)์ด ํ์ํ ์ด์ ์ ๋ํ์ฌ ์ค๋ช ํด์ฃผ์ธ์.
์ฒซ์งธ๋ก CSR์ ๋นํด ๋น ๋ฅธ FP(First Paint)์ FCP(First Contentful Paint)๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ฆ, ์ด๊ธฐ ํ์ด์ง ๋ก๋๊ฐ ๋ ๋น ๋ฅด๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค. SSR์ ์๋ฒ์์ ํ์ด์ง๋ฅผ ๋ ๋๋ง(pre-rendering)ํ ํ ์ ์ ์๊ฒ ๋จผ์ ์ธํฐ๋์ ์ด ๋ถ๊ฐ๋ฅํ ํ์ด์ง๋ฅผ ์ ๋ฌํด์ฃผ๊ณ (TTV) ์ดํ ๋ธ๋ผ์ฐ์ ์์ JS ๊ตฌ๋ฌธ์ ์ฝ์ผ๋ฉฐ ์ํ(hydration) ๊ณผ์ ์ ๊ฑฐ์ณ ์ธํฐ๋์ ์ด ๊ฐ๋ฅํด์ง๋(TTI) ์์ผ๋ก ์คํ๋ฉ๋๋ค. ์ด ๊ณผ์ ์ ํตํด ์ ์ ๋ CSR์ ๋นํด ๋น ๋ฅด๊ฒ ์ด๊ธฐ ํ๋ฉด์ ๋ณด๊ฒ ๋๊ณ , ์ด๋ '์๋น์ค๊ฐ ๋น ๋ฅด๋ค'๋ ์ธ์์ ์ค ์ ์๊ธฐ์ ์ฌ์ฉ์ ๊ฒฝํ์ด ์ข์์ง ์ ์์ต๋๋ค.
๋์งธ๋ก ๋ ๋์ SEO๋ฅผ ์ํด ํ์ํฉ๋๋ค. SSR์ ์ฌ์ฉํ๊ฒ ๋๋ฉด ํฌ๋กค๋ฌ๊ฐ ์ฝ๊ฒ ํด์ํ ์ ์๋ ํํ๋ก์จ ์ ๊ณตํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ํฌ๋กค๋ฌ๋ ํด๋น ์น ์ฌ์ดํธ์ ์๋ ๋งํฌ๋ค์ ํ๊ณ ๋ค๋๋ฉฐ ์๋ก์ด ํ์ด์ง๋ฅผ ์ฐพ๋ ์ผ์ ๊ณ์ ์ํํ๊ณ , ์ด๋ ๊ฒ ๋ค์ด๊ฐ ์นํ์ด์ง์ ์ ๋ณด๋ค์ ๊ตฌ๊ธ ๊ฒ์ ์์ธ์ ์ ์ฅํ๋ ์ญํ ์ ์ํํฉ๋๋ค. ํ์ง๋ง SPA์์ CSR๋ก ์ ์๋ ์๋น์ค์์๋ ๊ทธ ์ญํ ์ ์ํํ๊ธฐ ์ด๋ ค์ด๋ฐ, ๊ทธ ์๋ก React ํ์ผ ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด 1๊ฐ์ html ํ์ผ ์์ ๋น <div>๋ง ์กด์ฌํ๋ ๊ฒ์ ๋ค ์ ์๋ค. ๋น div ํ๊ทธ ์์๋ ๋ ๋๋ง ์ดํ ๋ด์ฉ๋ค์ด ์ฑ์์ง๊ฒ ๋์ง๋ง ๊ทธ ์ ๊น์ง๋ ๋ง ๊ทธ๋๋ก '๋น์ด ์๋' ์ํ์ด๊ธฐ์ ํฌ๋กค๋ฌ๊ฐ ์ํ๋ ์ ๋ณด๋ฅผ ์ฐพ๊ธฐ ์ด๋ ต๊ณ ์ด๊ฒ์ด ๊ฒ์ ์์ง ์ต์ ํ์ ์ํฅ์ ์ฃผ๊ฒ ๋ฉ๋๋ค. ์ด์ ๋ฏธ๋ฆฌ ๊ทธ๋ฆฐ ํ์ด์ง๋ฅผ ์ ๋ฌํ๋ SSR์ ํฌ๋กค๋ฌ๊ฐ ์ ๋ณด๋ฅผ ์ฐพ๊ณ ์์ธํ๊ธฐ ์ ํฉํ๊ธฐ์ ํ์ํฉ๋๋ค.
๐ก Next.js ํ๋ก์ ํธ๋ฅผ ์ธํ ํ ๋ค yarn start ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ ๋ ์คํ๋๋ ์ฝ๋๋ฅผ next.js github ๋ ํฌ์งํ ๋ฆฌ์์ ์ฐพ์ ๋ค, ํด๋น ํ์ผ์ ๋ํ ๊ฐ๋จํ ์ค๋ช ์ ์ฒจ๋ถํด์ฃผ์ธ์.
์ฐ์ next.js github ๋ ํฌ์งํ ๋ฆฌ์์ ์ฐพ์ ์คํ๋๋ ์ฝ๋์ ๊ฒฝ๋ก๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
next.js/packages/next/cli/next-start.ts
์ฐ์ ์๋ชป๋ ํค์๋๋ก ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ์ ๋์ ๋ํ ์๋ด ํน์ build๋ฅผ ์งํํ์ง ์๊ณ start๋ฅผ ์งํํ์ ๋์ ๋ํ ์๋ด์ ๋ํ ๋ด์ฉ์ด ์ด๋ฐ์ ์กด์ฌํฉ๋๋ค. --help, --port ๋ฑ์ cliCommand๋ --h์ ๊ฐ์ alias์ ๋ํ ์๋ด๋ ์กด์ฌํฉ๋๋ค.
์ดํ ๋ฌธ์ ์์ด ์งํ๋๋ฉด 0.0.0.0 ๋๋ --hostname์ผ๋ก ์ง์ ํ ๊ฐ๊ณผ ํจ๊ป `:ํฌํธ ๋๋ฒ` ๊ฐ ๋ถ์ ๋งํฌ๋ฅผ ํฐ๋ฏธ๋๋ก ๋ณด์ฌ์ฃผ๋ฉฐ ์ฑ์ด ์ค๋น๋์์์ ์๋ ค์ค๋๋ค. ์ฑ์ `_app.js` (ํ์ ์คํฌ๋ฆฝํธ๋ผ๋ฉด _app.tsx), `_document.js` ํ์ผ์ด ์ฐจ๋ก๋ก ์คํ๋๋ฉฐ ์ ์ํ ํ์ด์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ฒ ๋ฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ๋ ํฌ์งํ ๋ฆฌ์์ ์ฐพ์ ์ฝ๋๋ฅผ ์ฒจ๋ถํฉ๋๋ค.
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'
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 }
๋๊ธ