CSR(Client-side Rendering)이란 웹 브라우저에 데이터들을 보여주는 마지막 작업인 컴포넌트들을 렌더링하는 작업을 하는 방법 중에 하나이다. 이미 렌더링에 필요한 로직들을 모두 Client(웹브라우저)에게 보내 Sever와 통신 없이도 Client의 요청에 따라 빠르게 렌더링 하는 방식을 말한다. 이 방식은 처음 모든 렌더링에 필요한 로직들을 보내 셋팅해야해야하기 때문에 초기에 로딩이 길어질 수 있다는 점과 SEO에 불리한 점이 단점입니다. 이와 상반되는 개념으로 SSR(Sever-side Rendering)이 있으며 장단점이 반대입니다.
SPA는 기본적으로 CSR으로 구현되어 있습니다. 그래야 하나의 페이지에서 Client 요청에 따라 어플리케이션을 작동시킬 수 있습니다. 그러므로 CSR의 단점을 가집니다. 1. 첫 로딩이 느린점. 이는 고객에게 서비스를 소개도 하지 못한채 바로 이탈할 수 있다는 점에서 크리티컬한 단점입니다. 2. SEO(검색 엔진 최적화)의 불리함. 대부분의 고객들은 Search를 통해 서비스에 유입합니다. 이를 위해 SEO에 맞는 서비스를 개발하여 고객들이 검색을 통해 쉽게 우리 서비스에 접근할 수 있도록 해야합니다.
이러한 문제점들을 해결하기 위해 SPA에 SSR이 필요합니다.
=> 창피하게도 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 가 샐행해 내가 위에 가장 먼저 언급한 해당 코드가 샐행된다.