728x90
원티드 챌린지 10월 (SSR, CSR, Next.js)에서 내준 과제입니다.
해당 과제를 이해조차 못했는데, 수강하시는 분들 중 좋은 블로그 글이 있어 참고하였습니다.
아니 그냥 거의 따라 해봤습니다. 😭
개발세계엔 대단하신 분들이 많아요~! 존경 존경
과제를 잘 해주셨다고 칭찬받으신 포스팅
Next.js 초기세팅에서 Yarn Start 하면 생기는 일
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
- yarn start를 하면 '_app.js' 파일이 먼저 실행되나 봅니다.
- 해당 파일은 프롭스로 { 컴포넌트와 페이지 프롭스를 받습니다. }
솔찍히
- 과제를 다시 하는 입장이고, 다른 분 블로그 보면서 하는건데도 뭔말인지 모르겠습니다.
- index.jsx 파일이 없는건 뭘까요? 핳ㅎㅎ
- 깃헙 레포에서 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'
const nextStart: cliCommand = (argv) => {
const validArgs: arg.Spec = {
// Types
'--help': Boolean,
// Aliases
}
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
}
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']
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 }
Server
- packages/next/server/lib/start-server.ts
- 솔찍히 잘 모르겠어서
{
...
return new Promise<NextServer>((resolve, reject) => {
let port = opts.port
let retryCount = 0
// 서버를 리스닝 하나보다.
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)
})
}
next가 나와서 next를 찾아 보셨단다.
- 따라가보자. 공포영화 느낌 난다. 뭐가 나올지 모르고 그냥 따라가는 주인공이 된 느낌.
export class NextServer {
constructor(options: NextServerOptions) {
this.options = options
}
get hostname() {
return this.options.hostname
}
get port() {
return this.options.port
}
getRequestHandler(): RequestHandler {
return async (
req: IncomingMessage,
res: ServerResponse,
parsedUrl?: UrlWithParsedQuery
) => {
const requestHandler = await this.getServerRequestHandler()
return requestHandler(req, res, parsedUrl)
}
}
getUpgradeHandler() {
return async (req: IncomingMessage, socket: any, head: any) => {
const server = await this.getServer()
// @ts-expect-error we mark this as protected so it
// causes an error here
return server.handleUpgrade.apply(server, [req, socket, head])
}
}
async prepare() {
const server = await this.getServer()
return server.prepare()
}
private async createServer(options: DevServerOptions): Promise<Server> {
if (options.dev) {
const DevServer = require('./dev/next-dev-server').default
return new DevServer(options)
}
const ServerImplementation = await getServerImpl()
return new ServerImplementation(options)
}
private async getServer() {
if (!this.serverPromise) {
setTimeout(getServerImpl, 10)
this.serverPromise = this.loadConfig().then(async (conf) => {
this.server = await this.createServer({
...this.options,
conf,
})
if (this.preparedAssetPrefix) {
this.server.setAssetPrefix(this.preparedAssetPrefix)
}
return this.server
})
}
return this.serverPromise
}
private async getServerRequestHandler() {
// Memoize request handler creation
if (!this.reqHandlerPromise) {
this.reqHandlerPromise = this.getServer().then((server) =>
server.getRequestHandler().bind(server)
)
}
return this.reqHandlerPromise
}
}
// This file is used for when users run `require('next')`
function createServer(options: NextServerOptions): NextServer {
if (options == null) {
throw new Error(
'The server has not been instantiated properly. https://nextjs.org/docs/messages/invalid-server-options'
)
}
if (
!('isNextDevCommand' in options) &&
process.env.NODE_ENV &&
!['production', 'development', 'test'].includes(process.env.NODE_ENV)
) {
log.warn(NON_STANDARD_NODE_ENV)
}
if (options.dev && typeof options.dev !== 'boolean') {
console.warn(
"Warning: 'dev' is not a boolean which could introduce unexpected behavior. https://nextjs.org/docs/messages/invalid-server-options"
)
}
if (shouldUseReactRoot) {
;(process.env as any).__NEXT_REACT_ROOT = 'true'
}
return new NextServer(options)
}
// Support commonjs `require('next')`
module.exports = createServer
exports = module.exports
// Support `import next from 'next'`
export default createServer
여기서부터 아득해지셨다고 한다. 난 시작부터 아득했다.
- 그래도 따라가보자...
- 안해보는것보단 나으니까 시키셨겠지...
protected async renderHTML(
req: NodeNextRequest,
res: NodeNextResponse,
pathname: string,
query: NextParsedUrlQuery,
renderOpts: RenderOpts
): Promise<RenderResult | null> {
// Due to the way we pass data by mutating `renderOpts`, we can't extend the
// object here but only updating its `serverComponentManifest` field.
// https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952
renderOpts.serverComponentManifest = this.serverComponentManifest
renderOpts.serverCSSManifest = this.serverCSSManifest
renderOpts.fontLoaderManifest = this.fontLoaderManifest
return renderToHTML(
req.originalRequest,
res.originalResponse,
pathname,
query,
renderOpts
)
}
renderToHTML()
- 아 이제 좀 알게 된게 있다.
- 일단 뭔가 나오면 해당 함수로 타고 들어 가면 된다.
- 물꼬만 트이면 그냥 의심되는 함수의 위치로 엄청 찾아 들어가면 된다.
- 그리고 if문이 엄청나게 많은데, 에러 처리 같은것들이 엄청 많은거라서 살짝 무시해주면 핵심 로직만 따라가는것이 훨씬 쉬워진다.
- 물론 진짜 쉬워지는건 아니지만, 그냥 훨씬 마음이 편해진달까...
// DOM 을 찾았습니다.
// 저는 이거 찾다 못찾아서 그냥 검색했어요.
// 허허허허허허허허탈하네요 ㅎㅎ
// 이것도 다 경험이겠죠 ㅎㅎㅎㅎㅎㅎ
const Body = ({ children }: { children: JSX.Element }) => {
return inAmpMode ? children : <div id="__next">{children}</div>
}
const renderDocument = async () => {
// Document를 Render해주는 함수 발견..... 😭
async function loadDocumentInitialProps(
renderShell?: (
_App: AppType,
_Component: NextComponentType
) => Promise<ReactReadableStream>
) {
const renderPage: RenderPage = async (
options: ComponentsEnhancer = {}
): Promise<RenderPageResult> => {
// 그러고 보면 if 문으로 분기해서 return 하는 애들은 다 생략하시네요.
// 예외처리 하는애들은 중요한 핵심 로직이 아니니 일단 제낀다?
// 이말인가요?
const html = await renderToString(
<Body>
<AppContainerWithIsomorphicFiberStructure>
{renderPageTree(EnhancedApp, EnhancedComponent, {
...props,
router,
})}
</AppContainerWithIsomorphicFiberStructure>
</Body>
)
return { html, head }
}
const documentCtx = { ...ctx, renderPage }
const docProps: DocumentInitialProps = await loadGetInitialProps(
Document,
documentCtx
)
return { docProps, documentCtx }
}
const renderContent = (_App: AppType, _Component: NextComponentType) => {
const EnhancedApp = _App || App
const EnhancedComponent = _Component || Component
return ctx.err && ErrorDebug ? (
<Body>
<ErrorDebug error={ctx.err} />
</Body>
) : (
<Body>
<AppContainerWithIsomorphicFiberStructure>
{renderPageTree(EnhancedApp, EnhancedComponent, {
...props,
router,
})}
</AppContainerWithIsomorphicFiberStructure>
</Body>
)
}
const documentResult = await renderDocument()
const document = (
<AmpStateContext.Provider value={ampState}>
<HtmlContext.Provider value={htmlProps}>
{documentResult.documentElement(htmlProps)}
</HtmlContext.Provider>
</AmpStateContext.Provider>
)
const documentHTML = await renderToStaticMarkup(document)
const [renderTargetPrefix, renderTargetSuffix] = documentHTML.split(
'<next-js-internal-body-render-target></next-js-internal-body-render-target>'
)
이제 클라이언트를 보신답니다...
- 폴더 구조도 잘 봐야하는구나...
- 폴더 구조를 먼저 살펴 보면서 어떤 내용이 있는지 큰 그림을 그리는건가??
- 아래 코드에서 이니셜라이즈 한 뒤에 하이드레잇 한다고 나와있다.
- 그래서 하이드레잇을 찾아 간다.
- import {} from './'이라 어떤 파일을 찾아야 되는지 이해가 안됐는데 index.tsx파일의 경로라고 한다. 신기하다. 처음 알았다.
import { initialize, hydrate, version, router, emitter } from './'
window.next = {
version,
// router is initialized later so it has to be live-binded
get router() {
return router
},
emitter,
}
initialize({})
.then(() => hydrate())
.catch(console.error)
client/index.tsx
- 막바지에 다왔다.
- 그냥 막무가내로 따라 가고 있지만, 그냥 또 한가지 배운건 있다.
- 일단 다 접고 함수 이름부터 보는게 은근히 도움이 될지도 모른다는 점?
export async function initialize(opts: { webpackHMR?: any } = {}): Promise<{...}> {}
function renderApp(App: AppComponent, appProps: AppProps){...}
function renderReactElement(){}
function doRender(input: RenderRouteInfo): Promise<any> {...}
async function render(renderingProps: RenderRouteInfo): Promise<void> {...}
export async function hydrate(opts?: { beforeRender?: () => Promise<void> }) {...}
...
...
...
...
...
...
function doRender(input: RenderRouteInfo): Promise<any> {
let { App, Component, props, err }: RenderRouteInfo = input
const appProps: AppProps = {
...props,
Component,
err,
router,
}
// ...뭔가 <style> 을 붙이고 있는 것 같음...
const elem: JSX.Element = (
<>
<Head callback={onHeadCommit} />
<AppContainer>
{renderApp(App, appProps)}
<Portal type="next-route-announcer">
<RouteAnnouncer />
</Portal>
</AppContainer>
</>
)
// We catch runtime errors using componentDidCatch which will trigger renderError
renderReactElement(appElement!, callback => (
<Root callbacks={[callback, onRootCommit]}>
{process.env.__NEXT_STRICT_MODE ? (
<React.StrictMode>{elem}</React.StrictMode>
) : (
elem
)}
</Root>
))
return renderPromise
}
마치며...
- 그러라고 주신 과제는 아니겠지만... 나의 부족함을 많이 느낀 과제였다.
- 부트캠프 출신 님들아!! 라는 이야기가 있는 것처럼 나 또한 부트캠프를 아직 졸업조차 못한 사람이라 ㅎㅎ 정말 한없이 부족함을 많이 느꼈다.
- 화이팅 해야지... 😭
다시 한번 언급 하지만 과제는 "지니코딩" 님의 과제를 참고하며 따라 가본 것입니다. 지니코딩 님 챌린지 과제 링크
'FrontEnd > Next.js' 카테고리의 다른 글
[Next.js] Next.js 에서 aos 사용법 (Animation On Scroll) (0) | 2022.12.22 |
---|---|
[Next.js] Next.js 특징, 프로젝트 초기 세팅 (0) | 2022.12.13 |
댓글