SlideShare uma empresa Scribd logo
1 de 39
Baixar para ler offline
CRA + SSR.
(feat. TypeScript)
승형수
20@kross.kr
승형수
국문과 -> 개발자
(주) 한국어음중개 | Frontend Developer
React, IaC, Cloud Infrastructure...
• Server-Side Rendering?
• ImplementaDon
• Checklist
• 스파게티는 먹을 때에만 | 서버 코드를 분리하자
• asset-manifest.json | build 안에 담긴 보물
• 직선보다 빠른 굴절 | TypeScript를 사용하는 편이 좋은 이유
• Extra
• 필수 패키지 설치 이슈
https://github.com/
huskyhoochu/ssr-react-app
Server-Side Rendering?
기존 웹서버
전체 데이터 담긴
Full - HTML
멋대로 비유하기
백화점에서 완제품 소파를 사온다
Server-Side Rendering?
+
Client-Side Rendering
최소한의 HTML
+
화면 전체 렌더링을 위한 JS
멋대로 비유하기
이케아에서 조립형 소파를 사온다
Server-Side Rendering?
+
Server-Side Rendering
초기 렌더링 마친 HTML
+
일부 업데이트를 위한 JS
(AJAX, InteracEon...)
Implement
서버에서 한번:
1. ReactDOMServer.renderToString()
함수로 react app을 html string 으로 변환
2. 서버의 '^/$' route 에서 출력하게 함
클라이언트에서 또 한번:
3. 'build/index.html'에는 react 런타임 코
드와 페이지 코드가 붙어 있음!
4. 모든 리소스를 staDc 으로 서버에 제공하자
render | hydrate
전체 HTML
렌더링
기존 HTML과 비교 후
부분 업데이트
ReactDOM.hydrate(<App />, document.getElementById('root'));
Checklist
스파게티는 먹을 때에만 | 서버 코드를 분리하자
asset-manifest.json | build 안에 담긴 보물
직선보다 빠른 굴절 | TypeScript를 사용하는 편이 좋은 이유
스파게티는
먹을 때에만
renderToString() 함수가 서버 코드와 섞이기
마련
bootstrap.js 파일을 추가로 작성해 각자의 코드
를 따로 불러올 것
git submodule 운용 가능
Server Client
bootstrap.js
express app
객체
renderToString()
결과물
app.listen() ...
Bad
export default () => {
const app: express.Application = express();
const router: express.Router = express.Router();
router.use('^/$', (req, res) => {
const html = ReactDOMServer.renderToString(<App />);
...
});
...
};
서버 코드 안에서
React를 직접 호출
Front - Server
지나친 커플링
Folder Structure
.
/build
/src
App.tsx
html.tsx
...
/server
index.tsx
htmlRenderer.tsx
...
bootstrap.js
기존 CRA 구조
서버 관련 코드 모음
최종 실행 파일
Good
// src/html.tsx
export default ReactDOMServer.renderToString(<App />);
// server/index.tsx
export default (
html: string,
paths: { buildPath: string, htmlPath: string },
): express.Application => {
const app: express.Application = express();
const router: express.Router = express.Router();
router.use('^/$', htmlRenderer(html, paths.htmlPath));
router.use(express.static(paths.buildPath, { maxAge: '1y' }));
app.use(router);
return app;
};
Good
// bootstrap.js
const listener = require('./server').default;
const html = require('./src/html').default;
const buildPath = path.resolve(__dirname, 'build');
const htmlPath = path.resolve(buildPath, 'index.html');
listener(html, { buildPath, htmlPath }).listen(5000, () => {
console.log('server listening on http://localhost:5000 ...');
});
html & path
매개변수로 제공
Front 환경이 바뀌어도
Server가 영향 받지 않음
git submodules
.
/build
/src
App.tsx
html.tsx
...
/server
index.tsx
htmlRenderer.tsx
...
bootstrap.js
.gitmodules
submodule:
git repo 내부의 또 다른 repo
참조 파일을 통해 관리
[submodule "ssr-server"]
path = server
url = https://github.com/내저장소/ssr-server.git
asset-
manifest.json
build 폴더에 생성된 모든 자산의 일람표
html string을 만들 때 staDc file의 참조를 가져다
쓸 수 있다
asset-
manifest.json
<img
src={logo}
className="App-logo"
alt="logo"
/>
+
"/static/media/logo.5d5d9eef.svg"
<img
src="/static/media/logo.5d5d9eef.svg"
class="App-logo"
alt="logo"
/>
logo.svg 파일 import 문을
해석하지 못하여
빈 객체가 그대로 출력됨
key: build 이전 원본 파일명
value: webpack이 bundling을 마친 파일명
Overwrite asset module's path
// bootstrap.js
const path = require('path');
const register = require('ignore-styles').default;
const assetManifest = require('./build/asset-manifest.json');
const searchAssetUrl = (manifest, filename) =>
Object.keys(manifest.files)
.filter(asset => asset.replace('static/media/', '') === filename)
.map(fileKey => manifest.files[fileKey]);
register(undefined, (module, filename) => {
const imgExtensions = ['.png', '.jpg', 'jpeg', '.gif', '.svg'];
const isImg = imgExtensions.find(ext => filename.endsWith(ext));
if (!isImg) return;
const [assetUrl] = searchAssetUrl(assetManifest, path.basename(filename));
module.exports = assetUrl;
});
ignore-styles:
node 실행 중
asset import module을
무시하게 해 주어 에러 방지
asset import module 등장 시
후처리 callback 제공
직선보다
빠른 굴절
왜 TypeScript가 SSR에 도움이 될까?
tsc 컴파일을 통해 CommonJS 파일 생성 가능
-> babel을 쓰지 않아 성능 향상 🚀 페르마의 원리
빛은 최단시간으로 이동할 수 있는 경로를 택한다
In JavaScript
// bootstrap.js
require('@babel/polyfill');
require('@babel/register')({
extensions: ['.js', '.jsx'],
ignore: [/(node_modules)/],
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['dynamic-import-node'],
});
// src/html.jsx
export default ReactDOMServer.renderToString(<App />); 소스를 JSX 파일로 가져옴소스를 JSX 파일로 가져옴
JSX를 node에서 실행할 수
없으므로 babel register 사용
@babel/polyfill
(async/await, Promise 핸들링)
dynamic-import-node
동적 import 구문 처리 플러그인
// tsconfig.ssr.json
... (기존 CRA tsconfig.json과 동일)
"noEmit": false,
"jsx": "react",
"rootDir": "src",
"outDir": "app_compiled"
},
"include": [
"src"
],
"exclude": [
"src/*.test.tsx"
]
}
Extra
필수 패키지 설치 이슈
Redux
서버 렌더링 단계에서 데이터를 미리 주입하고 싶을
때만 사용
index.html에 인라인으로 window 전역 store
객체 삽입 -> 클라이언트 코드가 읽어들임
(공식 문서에서 권하는 방식)
https://redux.js.org/recipes/server-
rendering
<script>
window.__PRELOADED_STATE__={}
</script>
+
React-
Router-
DOM
html 생성 단계에서 StaticRouter 를 App 위에
Wrapping
서버 또한'*' 경로 라우터를 추가해 react-router
가 만든 path로 접속되더라도 html을 반환하도록 처
리
// server/index.ts
router.use('*', htmlRenderer);
// src/html.tsx
const routerContext = {};
<StaticRouter
location={req.baseUrl}
context={routerContext}
>
<App />
</StaticRouter>
React-
Loadable
모듈 이름을 특정해줘야
서버 렌더링 시에 모듈 추적을 할 수 있음!
const Index = Loadable({
loader: () => import(/* webpackChunkName: "IndexChunk" */
'../components/view/index'),
loading: () => null,
modules: ['IndexChunk'],
});
전체 패키지 적용 후
// src/html.tsx
module.exports = (req: any) => {
const csrfToken = req.csrfToken();
const store = configureStore();
store.dispatch(addCsrfToken(csrfToken));
const modules = [];
const routerContext = {};
const pushModule = (moduleName: string) => modules.push(moduleName);
const html = ReactDOM.renderToString(
<Capture report={pushModule}>
<Provider store={store}>
<StaticRouter
location={req.baseUrl}
context={routerContext}
>
<App />
</StaticRouter>
</Provider>
</Capture>,
);
const helmet = Helmet.renderStatic();
const preloadedState = JSON.stringify(store.getState());
return {
helmet,
html,
preloadedState,
};
};
Redux Store 정의
CSRF 토큰 전달받아 주입
Loadable.Capture
어떤 loadable 컴포넌트가 렌더링되는지
추적
StaEcRouter
locaEon이 변하지 않는 라우터
첫 접속 이후엔 SPA 형태가 될 것이므로 필요
// src/index.tsx
declare global {
interface Window {
__PRELOADED_STATE__: object;
}
}
const store = configureStore(window.__PRELOADED_STATE__);
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root'),
);
전역 객체 declare
// server/htmlRenderer.ts
return res.send(
htmlData
.replace('<div id="root"></div>', `<div id="root">${result.html}</div>`)
.replace(
'<title>React App</title>',
result.helmet.title.toString() + result.helmet.meta.toString(),
)
.replace(
'<script type="text/javascript">window.__PRELOADED_STATE__={}</script>',
`<script type="text/javascript">window.__PRELOADED_STATE__=${
result.preloadedState
}</script>`,
),
);
// bootstrap.js
Loadable.preloadAll().then(() => {
app.listen(PORT, '0.0.0.0', () => {
console.log(`Listening on http://localhost:${PORT} ...`);
});
});
성공보다 빠른 시행착오를
응원합니다

Mais conteúdo relacionado

Mais procurados

React state
React  stateReact  state
React stateDucat
 
HTTP/3 시대의 웹 성능 최적화 기술 이해하기
HTTP/3 시대의 웹 성능 최적화 기술 이해하기HTTP/3 시대의 웹 성능 최적화 기술 이해하기
HTTP/3 시대의 웹 성능 최적화 기술 이해하기SangJin Kang
 
Lets make a better react form
Lets make a better react formLets make a better react form
Lets make a better react formYao Nien Chung
 
React + Redux Introduction
React + Redux IntroductionReact + Redux Introduction
React + Redux IntroductionNikolaus Graf
 
ReactJS presentation
ReactJS presentationReactJS presentation
ReactJS presentationThanh Tuong
 
React JS: A Secret Preview
React JS: A Secret PreviewReact JS: A Secret Preview
React JS: A Secret Previewvaluebound
 
Facebook은 React를 왜 만들었을까?
Facebook은 React를 왜 만들었을까? Facebook은 React를 왜 만들었을까?
Facebook은 React를 왜 만들었을까? Kim Hunmin
 
4. 대용량 아키텍쳐 설계 패턴
4. 대용량 아키텍쳐 설계 패턴4. 대용량 아키텍쳐 설계 패턴
4. 대용량 아키텍쳐 설계 패턴Terry Cho
 
NextJS - Online Summit for Frontend Developers September 2020
NextJS - Online Summit for Frontend Developers September 2020NextJS - Online Summit for Frontend Developers September 2020
NextJS - Online Summit for Frontend Developers September 2020Milad Heydari
 
[125]웹 성능 최적화에 필요한 브라우저의 모든 것
[125]웹 성능 최적화에 필요한 브라우저의 모든 것[125]웹 성능 최적화에 필요한 브라우저의 모든 것
[125]웹 성능 최적화에 필요한 브라우저의 모든 것NAVER D2
 
1. 아키텍쳐 설계 프로세스
1. 아키텍쳐 설계 프로세스1. 아키텍쳐 설계 프로세스
1. 아키텍쳐 설계 프로세스Terry Cho
 
AWS 기반 마이크로 프론트엔드 아키텍처 구축하기
AWS 기반 마이크로 프론트엔드 아키텍처 구축하기AWS 기반 마이크로 프론트엔드 아키텍처 구축하기
AWS 기반 마이크로 프론트엔드 아키텍처 구축하기Eunsu Kim
 

Mais procurados (20)

React state
React  stateReact  state
React state
 
Its time to React.js
Its time to React.jsIts time to React.js
Its time to React.js
 
HTTP/3 시대의 웹 성능 최적화 기술 이해하기
HTTP/3 시대의 웹 성능 최적화 기술 이해하기HTTP/3 시대의 웹 성능 최적화 기술 이해하기
HTTP/3 시대의 웹 성능 최적화 기술 이해하기
 
React js
React jsReact js
React js
 
Lets make a better react form
Lets make a better react formLets make a better react form
Lets make a better react form
 
React + Redux Introduction
React + Redux IntroductionReact + Redux Introduction
React + Redux Introduction
 
ReactJS presentation
ReactJS presentationReactJS presentation
ReactJS presentation
 
React JS: A Secret Preview
React JS: A Secret PreviewReact JS: A Secret Preview
React JS: A Secret Preview
 
Facebook은 React를 왜 만들었을까?
Facebook은 React를 왜 만들었을까? Facebook은 React를 왜 만들었을까?
Facebook은 React를 왜 만들었을까?
 
Node.js 기본
Node.js 기본Node.js 기본
Node.js 기본
 
4. 대용량 아키텍쳐 설계 패턴
4. 대용량 아키텍쳐 설계 패턴4. 대용량 아키텍쳐 설계 패턴
4. 대용량 아키텍쳐 설계 패턴
 
NEXT.JS
NEXT.JSNEXT.JS
NEXT.JS
 
Reactjs
ReactjsReactjs
Reactjs
 
reactJS
reactJSreactJS
reactJS
 
Workshop 21: React Router
Workshop 21: React RouterWorkshop 21: React Router
Workshop 21: React Router
 
NextJS - Online Summit for Frontend Developers September 2020
NextJS - Online Summit for Frontend Developers September 2020NextJS - Online Summit for Frontend Developers September 2020
NextJS - Online Summit for Frontend Developers September 2020
 
[125]웹 성능 최적화에 필요한 브라우저의 모든 것
[125]웹 성능 최적화에 필요한 브라우저의 모든 것[125]웹 성능 최적화에 필요한 브라우저의 모든 것
[125]웹 성능 최적화에 필요한 브라우저의 모든 것
 
1. 아키텍쳐 설계 프로세스
1. 아키텍쳐 설계 프로세스1. 아키텍쳐 설계 프로세스
1. 아키텍쳐 설계 프로세스
 
Angular
AngularAngular
Angular
 
AWS 기반 마이크로 프론트엔드 아키텍처 구축하기
AWS 기반 마이크로 프론트엔드 아키텍처 구축하기AWS 기반 마이크로 프론트엔드 아키텍처 구축하기
AWS 기반 마이크로 프론트엔드 아키텍처 구축하기
 

Semelhante a Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)

Node.js and react
Node.js and reactNode.js and react
Node.js and reactHyungKuIm
 
Startup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSStartup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSCirculus
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs기동 이
 
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기현철 조
 
Mean 스택을 사용한 IoT 개발
Mean 스택을 사용한 IoT 개발Mean 스택을 사용한 IoT 개발
Mean 스택을 사용한 IoT 개발Jay Park
 
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]WSConf.
 
20131217 html5
20131217 html520131217 html5
20131217 html5DK Lee
 
7가지 동시성 모델 람다아키텍처
7가지 동시성 모델  람다아키텍처7가지 동시성 모델  람다아키텍처
7가지 동시성 모델 람다아키텍처Sunggon Song
 
RHQ 공감 Seminar 6th
RHQ 공감 Seminar 6thRHQ 공감 Seminar 6th
RHQ 공감 Seminar 6thTed Won
 
막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)연웅 조
 
11.react router dom
11.react router dom11.react router dom
11.react router domDaniel Lim
 
Vue.js 기초 실습.pptx
Vue.js 기초 실습.pptxVue.js 기초 실습.pptx
Vue.js 기초 실습.pptxwonyong hwang
 
Express framework tutorial
Express framework tutorialExpress framework tutorial
Express framework tutorial우림 류
 
Front-end Development Process - 어디까지 개선할 수 있나
Front-end Development Process - 어디까지 개선할 수 있나Front-end Development Process - 어디까지 개선할 수 있나
Front-end Development Process - 어디까지 개선할 수 있나JeongHun Byeon
 
PHP Slim Framework with Angular
PHP Slim Framework with AngularPHP Slim Framework with Angular
PHP Slim Framework with AngularJT Jintae Jung
 

Semelhante a Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript) (20)

Node.js and react
Node.js and reactNode.js and react
Node.js and react
 
4-3. jquery
4-3. jquery4-3. jquery
4-3. jquery
 
Nodejs express
Nodejs expressNodejs express
Nodejs express
 
Startup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSStartup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JS
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs
 
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
[NDC17] Unreal.js - 자바스크립트로 쉽고 빠른 UE4 개발하기
 
Mean 스택을 사용한 IoT 개발
Mean 스택을 사용한 IoT 개발Mean 스택을 사용한 IoT 개발
Mean 스택을 사용한 IoT 개발
 
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
조은 - AMP PWA 101 [WSConf.Seoul.2017. Vol.2]
 
[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초
 
20131217 html5
20131217 html520131217 html5
20131217 html5
 
요즘웹개발
요즘웹개발요즘웹개발
요즘웹개발
 
7가지 동시성 모델 람다아키텍처
7가지 동시성 모델  람다아키텍처7가지 동시성 모델  람다아키텍처
7가지 동시성 모델 람다아키텍처
 
RHQ 공감 Seminar 6th
RHQ 공감 Seminar 6thRHQ 공감 Seminar 6th
RHQ 공감 Seminar 6th
 
Html5 performance
Html5 performanceHtml5 performance
Html5 performance
 
막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)
 
11.react router dom
11.react router dom11.react router dom
11.react router dom
 
Vue.js 기초 실습.pptx
Vue.js 기초 실습.pptxVue.js 기초 실습.pptx
Vue.js 기초 실습.pptx
 
Express framework tutorial
Express framework tutorialExpress framework tutorial
Express framework tutorial
 
Front-end Development Process - 어디까지 개선할 수 있나
Front-end Development Process - 어디까지 개선할 수 있나Front-end Development Process - 어디까지 개선할 수 있나
Front-end Development Process - 어디까지 개선할 수 있나
 
PHP Slim Framework with Angular
PHP Slim Framework with AngularPHP Slim Framework with Angular
PHP Slim Framework with Angular
 

Último

데이터 분석 문제 해결을 위한 나의 JMP 활용법
데이터 분석 문제 해결을 위한 나의 JMP 활용법데이터 분석 문제 해결을 위한 나의 JMP 활용법
데이터 분석 문제 해결을 위한 나의 JMP 활용법JMP Korea
 
JMP를 활용한 전자/반도체 산업 Yield Enhancement Methodology
JMP를 활용한 전자/반도체 산업 Yield Enhancement MethodologyJMP를 활용한 전자/반도체 산업 Yield Enhancement Methodology
JMP를 활용한 전자/반도체 산업 Yield Enhancement MethodologyJMP Korea
 
(독서광) 인간이 초대한 대형 참사 - 대형 참사가 일어날 때까지 사람들은 무엇을 하고 있었는가?
(독서광) 인간이 초대한 대형 참사 - 대형 참사가 일어날 때까지 사람들은 무엇을 하고 있었는가?(독서광) 인간이 초대한 대형 참사 - 대형 참사가 일어날 때까지 사람들은 무엇을 하고 있었는가?
(독서광) 인간이 초대한 대형 참사 - 대형 참사가 일어날 때까지 사람들은 무엇을 하고 있었는가?Jay Park
 
실험 설계의 평가 방법: Custom Design을 중심으로 반응인자 최적화 및 Criteria 해석
실험 설계의 평가 방법: Custom Design을 중심으로 반응인자 최적화 및 Criteria 해석실험 설계의 평가 방법: Custom Design을 중심으로 반응인자 최적화 및 Criteria 해석
실험 설계의 평가 방법: Custom Design을 중심으로 반응인자 최적화 및 Criteria 해석JMP Korea
 
JMP를 활용한 가속열화 분석 사례
JMP를 활용한 가속열화 분석 사례JMP를 활용한 가속열화 분석 사례
JMP를 활용한 가속열화 분석 사례JMP Korea
 
JMP 기능의 확장 및 내재화의 핵심 JMP-Python 소개
JMP 기능의 확장 및 내재화의 핵심 JMP-Python 소개JMP 기능의 확장 및 내재화의 핵심 JMP-Python 소개
JMP 기능의 확장 및 내재화의 핵심 JMP-Python 소개JMP Korea
 
JMP가 걸어온 여정, 새로운 도약 JMP 18!
JMP가 걸어온 여정, 새로운 도약 JMP 18!JMP가 걸어온 여정, 새로운 도약 JMP 18!
JMP가 걸어온 여정, 새로운 도약 JMP 18!JMP Korea
 
공학 관점에서 바라본 JMP 머신러닝 최적화
공학 관점에서 바라본 JMP 머신러닝 최적화공학 관점에서 바라본 JMP 머신러닝 최적화
공학 관점에서 바라본 JMP 머신러닝 최적화JMP Korea
 

Último (8)

데이터 분석 문제 해결을 위한 나의 JMP 활용법
데이터 분석 문제 해결을 위한 나의 JMP 활용법데이터 분석 문제 해결을 위한 나의 JMP 활용법
데이터 분석 문제 해결을 위한 나의 JMP 활용법
 
JMP를 활용한 전자/반도체 산업 Yield Enhancement Methodology
JMP를 활용한 전자/반도체 산업 Yield Enhancement MethodologyJMP를 활용한 전자/반도체 산업 Yield Enhancement Methodology
JMP를 활용한 전자/반도체 산업 Yield Enhancement Methodology
 
(독서광) 인간이 초대한 대형 참사 - 대형 참사가 일어날 때까지 사람들은 무엇을 하고 있었는가?
(독서광) 인간이 초대한 대형 참사 - 대형 참사가 일어날 때까지 사람들은 무엇을 하고 있었는가?(독서광) 인간이 초대한 대형 참사 - 대형 참사가 일어날 때까지 사람들은 무엇을 하고 있었는가?
(독서광) 인간이 초대한 대형 참사 - 대형 참사가 일어날 때까지 사람들은 무엇을 하고 있었는가?
 
실험 설계의 평가 방법: Custom Design을 중심으로 반응인자 최적화 및 Criteria 해석
실험 설계의 평가 방법: Custom Design을 중심으로 반응인자 최적화 및 Criteria 해석실험 설계의 평가 방법: Custom Design을 중심으로 반응인자 최적화 및 Criteria 해석
실험 설계의 평가 방법: Custom Design을 중심으로 반응인자 최적화 및 Criteria 해석
 
JMP를 활용한 가속열화 분석 사례
JMP를 활용한 가속열화 분석 사례JMP를 활용한 가속열화 분석 사례
JMP를 활용한 가속열화 분석 사례
 
JMP 기능의 확장 및 내재화의 핵심 JMP-Python 소개
JMP 기능의 확장 및 내재화의 핵심 JMP-Python 소개JMP 기능의 확장 및 내재화의 핵심 JMP-Python 소개
JMP 기능의 확장 및 내재화의 핵심 JMP-Python 소개
 
JMP가 걸어온 여정, 새로운 도약 JMP 18!
JMP가 걸어온 여정, 새로운 도약 JMP 18!JMP가 걸어온 여정, 새로운 도약 JMP 18!
JMP가 걸어온 여정, 새로운 도약 JMP 18!
 
공학 관점에서 바라본 JMP 머신러닝 최적화
공학 관점에서 바라본 JMP 머신러닝 최적화공학 관점에서 바라본 JMP 머신러닝 최적화
공학 관점에서 바라본 JMP 머신러닝 최적화
 

Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)

  • 1. CRA + SSR. (feat. TypeScript) 승형수 20@kross.kr
  • 2. 승형수 국문과 -> 개발자 (주) 한국어음중개 | Frontend Developer React, IaC, Cloud Infrastructure...
  • 3. • Server-Side Rendering? • ImplementaDon • Checklist • 스파게티는 먹을 때에만 | 서버 코드를 분리하자 • asset-manifest.json | build 안에 담긴 보물 • 직선보다 빠른 굴절 | TypeScript를 사용하는 편이 좋은 이유 • Extra • 필수 패키지 설치 이슈
  • 5. Server-Side Rendering? 기존 웹서버 전체 데이터 담긴 Full - HTML 멋대로 비유하기 백화점에서 완제품 소파를 사온다
  • 6. Server-Side Rendering? + Client-Side Rendering 최소한의 HTML + 화면 전체 렌더링을 위한 JS 멋대로 비유하기 이케아에서 조립형 소파를 사온다
  • 7. Server-Side Rendering? + Server-Side Rendering 초기 렌더링 마친 HTML + 일부 업데이트를 위한 JS (AJAX, InteracEon...)
  • 8.
  • 9.
  • 10. Implement 서버에서 한번: 1. ReactDOMServer.renderToString() 함수로 react app을 html string 으로 변환 2. 서버의 '^/$' route 에서 출력하게 함 클라이언트에서 또 한번: 3. 'build/index.html'에는 react 런타임 코 드와 페이지 코드가 붙어 있음! 4. 모든 리소스를 staDc 으로 서버에 제공하자
  • 11.
  • 12.
  • 13. render | hydrate 전체 HTML 렌더링 기존 HTML과 비교 후 부분 업데이트 ReactDOM.hydrate(<App />, document.getElementById('root'));
  • 14. Checklist 스파게티는 먹을 때에만 | 서버 코드를 분리하자 asset-manifest.json | build 안에 담긴 보물 직선보다 빠른 굴절 | TypeScript를 사용하는 편이 좋은 이유
  • 15. 스파게티는 먹을 때에만 renderToString() 함수가 서버 코드와 섞이기 마련 bootstrap.js 파일을 추가로 작성해 각자의 코드 를 따로 불러올 것 git submodule 운용 가능 Server Client bootstrap.js express app 객체 renderToString() 결과물 app.listen() ...
  • 16. Bad export default () => { const app: express.Application = express(); const router: express.Router = express.Router(); router.use('^/$', (req, res) => { const html = ReactDOMServer.renderToString(<App />); ... }); ... }; 서버 코드 안에서 React를 직접 호출 Front - Server 지나친 커플링
  • 18. Good // src/html.tsx export default ReactDOMServer.renderToString(<App />); // server/index.tsx export default ( html: string, paths: { buildPath: string, htmlPath: string }, ): express.Application => { const app: express.Application = express(); const router: express.Router = express.Router(); router.use('^/$', htmlRenderer(html, paths.htmlPath)); router.use(express.static(paths.buildPath, { maxAge: '1y' })); app.use(router); return app; };
  • 19. Good // bootstrap.js const listener = require('./server').default; const html = require('./src/html').default; const buildPath = path.resolve(__dirname, 'build'); const htmlPath = path.resolve(buildPath, 'index.html'); listener(html, { buildPath, htmlPath }).listen(5000, () => { console.log('server listening on http://localhost:5000 ...'); }); html & path 매개변수로 제공 Front 환경이 바뀌어도 Server가 영향 받지 않음
  • 20. git submodules . /build /src App.tsx html.tsx ... /server index.tsx htmlRenderer.tsx ... bootstrap.js .gitmodules submodule: git repo 내부의 또 다른 repo 참조 파일을 통해 관리 [submodule "ssr-server"] path = server url = https://github.com/내저장소/ssr-server.git
  • 21. asset- manifest.json build 폴더에 생성된 모든 자산의 일람표 html string을 만들 때 staDc file의 참조를 가져다 쓸 수 있다 asset- manifest.json <img src={logo} className="App-logo" alt="logo" /> + "/static/media/logo.5d5d9eef.svg" <img src="/static/media/logo.5d5d9eef.svg" class="App-logo" alt="logo" />
  • 22. logo.svg 파일 import 문을 해석하지 못하여 빈 객체가 그대로 출력됨
  • 23. key: build 이전 원본 파일명 value: webpack이 bundling을 마친 파일명
  • 24. Overwrite asset module's path // bootstrap.js const path = require('path'); const register = require('ignore-styles').default; const assetManifest = require('./build/asset-manifest.json'); const searchAssetUrl = (manifest, filename) => Object.keys(manifest.files) .filter(asset => asset.replace('static/media/', '') === filename) .map(fileKey => manifest.files[fileKey]); register(undefined, (module, filename) => { const imgExtensions = ['.png', '.jpg', 'jpeg', '.gif', '.svg']; const isImg = imgExtensions.find(ext => filename.endsWith(ext)); if (!isImg) return; const [assetUrl] = searchAssetUrl(assetManifest, path.basename(filename)); module.exports = assetUrl; }); ignore-styles: node 실행 중 asset import module을 무시하게 해 주어 에러 방지 asset import module 등장 시 후처리 callback 제공
  • 25.
  • 26. 직선보다 빠른 굴절 왜 TypeScript가 SSR에 도움이 될까? tsc 컴파일을 통해 CommonJS 파일 생성 가능 -> babel을 쓰지 않아 성능 향상 🚀 페르마의 원리 빛은 최단시간으로 이동할 수 있는 경로를 택한다
  • 27. In JavaScript // bootstrap.js require('@babel/polyfill'); require('@babel/register')({ extensions: ['.js', '.jsx'], ignore: [/(node_modules)/], presets: ['@babel/preset-env', '@babel/preset-react'], plugins: ['dynamic-import-node'], }); // src/html.jsx export default ReactDOMServer.renderToString(<App />); 소스를 JSX 파일로 가져옴소스를 JSX 파일로 가져옴 JSX를 node에서 실행할 수 없으므로 babel register 사용 @babel/polyfill (async/await, Promise 핸들링) dynamic-import-node 동적 import 구문 처리 플러그인
  • 28.
  • 29. // tsconfig.ssr.json ... (기존 CRA tsconfig.json과 동일) "noEmit": false, "jsx": "react", "rootDir": "src", "outDir": "app_compiled" }, "include": [ "src" ], "exclude": [ "src/*.test.tsx" ] }
  • 31. Redux 서버 렌더링 단계에서 데이터를 미리 주입하고 싶을 때만 사용 index.html에 인라인으로 window 전역 store 객체 삽입 -> 클라이언트 코드가 읽어들임 (공식 문서에서 권하는 방식) https://redux.js.org/recipes/server- rendering <script> window.__PRELOADED_STATE__={} </script> +
  • 32. React- Router- DOM html 생성 단계에서 StaticRouter 를 App 위에 Wrapping 서버 또한'*' 경로 라우터를 추가해 react-router 가 만든 path로 접속되더라도 html을 반환하도록 처 리 // server/index.ts router.use('*', htmlRenderer); // src/html.tsx const routerContext = {}; <StaticRouter location={req.baseUrl} context={routerContext} > <App /> </StaticRouter>
  • 33. React- Loadable 모듈 이름을 특정해줘야 서버 렌더링 시에 모듈 추적을 할 수 있음! const Index = Loadable({ loader: () => import(/* webpackChunkName: "IndexChunk" */ '../components/view/index'), loading: () => null, modules: ['IndexChunk'], });
  • 35. // src/html.tsx module.exports = (req: any) => { const csrfToken = req.csrfToken(); const store = configureStore(); store.dispatch(addCsrfToken(csrfToken)); const modules = []; const routerContext = {}; const pushModule = (moduleName: string) => modules.push(moduleName); const html = ReactDOM.renderToString( <Capture report={pushModule}> <Provider store={store}> <StaticRouter location={req.baseUrl} context={routerContext} > <App /> </StaticRouter> </Provider> </Capture>, ); const helmet = Helmet.renderStatic(); const preloadedState = JSON.stringify(store.getState()); return { helmet, html, preloadedState, }; }; Redux Store 정의 CSRF 토큰 전달받아 주입 Loadable.Capture 어떤 loadable 컴포넌트가 렌더링되는지 추적 StaEcRouter locaEon이 변하지 않는 라우터 첫 접속 이후엔 SPA 형태가 될 것이므로 필요
  • 36. // src/index.tsx declare global { interface Window { __PRELOADED_STATE__: object; } } const store = configureStore(window.__PRELOADED_STATE__); ReactDOM.hydrate( <Provider store={store}> <BrowserRouter> <App /> </BrowserRouter> </Provider>, document.getElementById('root'), ); 전역 객체 declare
  • 37. // server/htmlRenderer.ts return res.send( htmlData .replace('<div id="root"></div>', `<div id="root">${result.html}</div>`) .replace( '<title>React App</title>', result.helmet.title.toString() + result.helmet.meta.toString(), ) .replace( '<script type="text/javascript">window.__PRELOADED_STATE__={}</script>', `<script type="text/javascript">window.__PRELOADED_STATE__=${ result.preloadedState }</script>`, ), );
  • 38. // bootstrap.js Loadable.preloadAll().then(() => { app.listen(PORT, '0.0.0.0', () => { console.log(`Listening on http://localhost:${PORT} ...`); }); });