SlideShare a Scribd company logo
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...)
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
Implement
서버에서 한번:
1. ReactDOMServer.renderToString()
함수로 react app을 html string 으로 변환
2. 서버의 '^/$' route 에서 출력하게 함
클라이언트에서 또 한번:
3. 'build/index.html'에는 react 런타임 코
드와 페이지 코드가 붙어 있음!
4. 모든 리소스를 staDc 으로 서버에 제공하자
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
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 제공
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
직선보다
빠른 굴절
왜 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 구문 처리 플러그인
Create-React-App으로 SSR을 구현하며 배운 점 (feat. TypeScript)
// 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} ...`);
});
});
성공보다 빠른 시행착오를
응원합니다
Ad

More Related Content

What's hot (20)

Introduction to Javascript
Introduction to JavascriptIntroduction to Javascript
Introduction to Javascript
Seble Nigussie
 
취향기반의 개인화 서비스를 통한 이커머스 혁신 – 소성운 ZIGZAG 데이터사이언티스트, 강상원 마이셀럽스 대표:: AWS Cloud We...
취향기반의 개인화 서비스를 통한 이커머스 혁신 – 소성운 ZIGZAG 데이터사이언티스트, 강상원 마이셀럽스 대표:: AWS Cloud We...취향기반의 개인화 서비스를 통한 이커머스 혁신 – 소성운 ZIGZAG 데이터사이언티스트, 강상원 마이셀럽스 대표:: AWS Cloud We...
취향기반의 개인화 서비스를 통한 이커머스 혁신 – 소성운 ZIGZAG 데이터사이언티스트, 강상원 마이셀럽스 대표:: AWS Cloud We...
Amazon Web Services Korea
 
Nextjs13.pptx
Nextjs13.pptxNextjs13.pptx
Nextjs13.pptx
DivyanshGupta922023
 
AWS 를 활용한 저지연 라이브 (Low Latency Live) 서비스 구현 - 류재춘 컨설턴트/에반젤리스트, GS Neot다 :: AW...
AWS 를 활용한 저지연 라이브 (Low Latency Live) 서비스 구현 - 류재춘 컨설턴트/에반젤리스트, GS Neot다 :: AW...AWS 를 활용한 저지연 라이브 (Low Latency Live) 서비스 구현 - 류재춘 컨설턴트/에반젤리스트, GS Neot다 :: AW...
AWS 를 활용한 저지연 라이브 (Low Latency Live) 서비스 구현 - 류재춘 컨설턴트/에반젤리스트, GS Neot다 :: AW...
Amazon Web Services Korea
 
Html5 tutorial for beginners
Html5 tutorial for beginnersHtml5 tutorial for beginners
Html5 tutorial for beginners
Singsys Pte Ltd
 
[D2 오픈세미나]1.무한스크롤성능개선
[D2 오픈세미나]1.무한스크롤성능개선[D2 오픈세미나]1.무한스크롤성능개선
[D2 오픈세미나]1.무한스크롤성능개선
NAVER D2
 
An introduction to React.js
An introduction to React.jsAn introduction to React.js
An introduction to React.js
Emanuele DelBono
 
RESTful API 설계
RESTful API 설계RESTful API 설계
RESTful API 설계
Jinho Yoo
 
ReactJS presentation.pptx
ReactJS presentation.pptxReactJS presentation.pptx
ReactJS presentation.pptx
DivyanshGupta922023
 
Microservice Architecture using Spring Boot with React & Redux
Microservice Architecture using Spring Boot with React & ReduxMicroservice Architecture using Spring Boot with React & Redux
Microservice Architecture using Spring Boot with React & Redux
NexThoughts Technologies
 
React JS - A quick introduction tutorial
React JS - A quick introduction tutorialReact JS - A quick introduction tutorial
React JS - A quick introduction tutorial
Mohammed Fazuluddin
 
게임서비스를 위한 ElastiCache 활용 전략 :: 구승모 솔루션즈 아키텍트 :: Gaming on AWS 2016
게임서비스를 위한 ElastiCache 활용 전략 :: 구승모 솔루션즈 아키텍트 :: Gaming on AWS 2016게임서비스를 위한 ElastiCache 활용 전략 :: 구승모 솔루션즈 아키텍트 :: Gaming on AWS 2016
게임서비스를 위한 ElastiCache 활용 전략 :: 구승모 솔루션즈 아키텍트 :: Gaming on AWS 2016
Amazon Web Services Korea
 
Introduction to React JS
Introduction to React JSIntroduction to React JS
Introduction to React JS
Arnold Asllani
 
[AWSKRUG] 혼자서 커뮤니티 귀동냥하며 만든 Next.js & Amplify & serverless framework 서버리스 웹 플...
[AWSKRUG] 혼자서 커뮤니티 귀동냥하며 만든 Next.js & Amplify & serverless framework 서버리스 웹 플...[AWSKRUG] 혼자서 커뮤니티 귀동냥하며 만든 Next.js & Amplify & serverless framework 서버리스 웹 플...
[AWSKRUG] 혼자서 커뮤니티 귀동냥하며 만든 Next.js & Amplify & serverless framework 서버리스 웹 플...
Tae-Seong Park
 
Node.js Express
Node.js  ExpressNode.js  Express
Node.js Express
Eyal Vardi
 
1. 아키텍쳐 설계 프로세스
1. 아키텍쳐 설계 프로세스1. 아키텍쳐 설계 프로세스
1. 아키텍쳐 설계 프로세스
Terry Cho
 
웹 프로그래밍 팀프로젝트 최종발표
웹 프로그래밍 팀프로젝트 최종발표웹 프로그래밍 팀프로젝트 최종발표
웹 프로그래밍 팀프로젝트 최종발표
Seong Heum Park
 
[AWS Hero 스페셜] Amazon Personalize를 통한 개인화/추천 서비스 개발 노하우 - 소성운(크로키닷컴) :: AWS C...
[AWS Hero 스페셜] Amazon Personalize를 통한 개인화/추천 서비스 개발 노하우 - 소성운(크로키닷컴) :: AWS C...[AWS Hero 스페셜] Amazon Personalize를 통한 개인화/추천 서비스 개발 노하우 - 소성운(크로키닷컴) :: AWS C...
[AWS Hero 스페셜] Amazon Personalize를 통한 개인화/추천 서비스 개발 노하우 - 소성운(크로키닷컴) :: AWS C...
AWSKRUG - AWS한국사용자모임
 
CSS and CSS3
CSS and CSS3CSS and CSS3
CSS and CSS3
Robyn Overstreet
 
React JS - Introduction
React JS - IntroductionReact JS - Introduction
React JS - Introduction
Sergey Romaneko
 
Introduction to Javascript
Introduction to JavascriptIntroduction to Javascript
Introduction to Javascript
Seble Nigussie
 
취향기반의 개인화 서비스를 통한 이커머스 혁신 – 소성운 ZIGZAG 데이터사이언티스트, 강상원 마이셀럽스 대표:: AWS Cloud We...
취향기반의 개인화 서비스를 통한 이커머스 혁신 – 소성운 ZIGZAG 데이터사이언티스트, 강상원 마이셀럽스 대표:: AWS Cloud We...취향기반의 개인화 서비스를 통한 이커머스 혁신 – 소성운 ZIGZAG 데이터사이언티스트, 강상원 마이셀럽스 대표:: AWS Cloud We...
취향기반의 개인화 서비스를 통한 이커머스 혁신 – 소성운 ZIGZAG 데이터사이언티스트, 강상원 마이셀럽스 대표:: AWS Cloud We...
Amazon Web Services Korea
 
AWS 를 활용한 저지연 라이브 (Low Latency Live) 서비스 구현 - 류재춘 컨설턴트/에반젤리스트, GS Neot다 :: AW...
AWS 를 활용한 저지연 라이브 (Low Latency Live) 서비스 구현 - 류재춘 컨설턴트/에반젤리스트, GS Neot다 :: AW...AWS 를 활용한 저지연 라이브 (Low Latency Live) 서비스 구현 - 류재춘 컨설턴트/에반젤리스트, GS Neot다 :: AW...
AWS 를 활용한 저지연 라이브 (Low Latency Live) 서비스 구현 - 류재춘 컨설턴트/에반젤리스트, GS Neot다 :: AW...
Amazon Web Services Korea
 
Html5 tutorial for beginners
Html5 tutorial for beginnersHtml5 tutorial for beginners
Html5 tutorial for beginners
Singsys Pte Ltd
 
[D2 오픈세미나]1.무한스크롤성능개선
[D2 오픈세미나]1.무한스크롤성능개선[D2 오픈세미나]1.무한스크롤성능개선
[D2 오픈세미나]1.무한스크롤성능개선
NAVER D2
 
An introduction to React.js
An introduction to React.jsAn introduction to React.js
An introduction to React.js
Emanuele DelBono
 
RESTful API 설계
RESTful API 설계RESTful API 설계
RESTful API 설계
Jinho Yoo
 
Microservice Architecture using Spring Boot with React & Redux
Microservice Architecture using Spring Boot with React & ReduxMicroservice Architecture using Spring Boot with React & Redux
Microservice Architecture using Spring Boot with React & Redux
NexThoughts Technologies
 
React JS - A quick introduction tutorial
React JS - A quick introduction tutorialReact JS - A quick introduction tutorial
React JS - A quick introduction tutorial
Mohammed Fazuluddin
 
게임서비스를 위한 ElastiCache 활용 전략 :: 구승모 솔루션즈 아키텍트 :: Gaming on AWS 2016
게임서비스를 위한 ElastiCache 활용 전략 :: 구승모 솔루션즈 아키텍트 :: Gaming on AWS 2016게임서비스를 위한 ElastiCache 활용 전략 :: 구승모 솔루션즈 아키텍트 :: Gaming on AWS 2016
게임서비스를 위한 ElastiCache 활용 전략 :: 구승모 솔루션즈 아키텍트 :: Gaming on AWS 2016
Amazon Web Services Korea
 
Introduction to React JS
Introduction to React JSIntroduction to React JS
Introduction to React JS
Arnold Asllani
 
[AWSKRUG] 혼자서 커뮤니티 귀동냥하며 만든 Next.js & Amplify & serverless framework 서버리스 웹 플...
[AWSKRUG] 혼자서 커뮤니티 귀동냥하며 만든 Next.js & Amplify & serverless framework 서버리스 웹 플...[AWSKRUG] 혼자서 커뮤니티 귀동냥하며 만든 Next.js & Amplify & serverless framework 서버리스 웹 플...
[AWSKRUG] 혼자서 커뮤니티 귀동냥하며 만든 Next.js & Amplify & serverless framework 서버리스 웹 플...
Tae-Seong Park
 
Node.js Express
Node.js  ExpressNode.js  Express
Node.js Express
Eyal Vardi
 
1. 아키텍쳐 설계 프로세스
1. 아키텍쳐 설계 프로세스1. 아키텍쳐 설계 프로세스
1. 아키텍쳐 설계 프로세스
Terry Cho
 
웹 프로그래밍 팀프로젝트 최종발표
웹 프로그래밍 팀프로젝트 최종발표웹 프로그래밍 팀프로젝트 최종발표
웹 프로그래밍 팀프로젝트 최종발표
Seong Heum Park
 
[AWS Hero 스페셜] Amazon Personalize를 통한 개인화/추천 서비스 개발 노하우 - 소성운(크로키닷컴) :: AWS C...
[AWS Hero 스페셜] Amazon Personalize를 통한 개인화/추천 서비스 개발 노하우 - 소성운(크로키닷컴) :: AWS C...[AWS Hero 스페셜] Amazon Personalize를 통한 개인화/추천 서비스 개발 노하우 - 소성운(크로키닷컴) :: AWS C...
[AWS Hero 스페셜] Amazon Personalize를 통한 개인화/추천 서비스 개발 노하우 - 소성운(크로키닷컴) :: AWS C...
AWSKRUG - AWS한국사용자모임
 

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

Node.js and react
Node.js and reactNode.js and react
Node.js and react
HyungKuIm
 
4-3. jquery
4-3. jquery4-3. jquery
4-3. jquery
JinKyoungHeo
 
Startup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSStartup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JS
Circulus
 
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.
 
Golang Project Guide from A to Z: From Feature Development to Enterprise Appl...
Golang Project Guide from A to Z: From Feature Development to Enterprise Appl...Golang Project Guide from A to Z: From Feature Development to Enterprise Appl...
Golang Project Guide from A to Z: From Feature Development to Enterprise Appl...
Kyuhyun Byun
 
[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초[Codelab 2017] ReactJS 기초
[Codelab 2017] ReactJS 기초
양재동 코드랩
 
20131217 html5
20131217 html520131217 html5
20131217 html5
DK Lee
 
요즘웹개발
요즘웹개발요즘웹개발
요즘웹개발
Lee MyoungKyu
 
7가지 동시성 모델 람다아키텍처
7가지 동시성 모델  람다아키텍처7가지 동시성 모델  람다아키텍처
7가지 동시성 모델 람다아키텍처
Sunggon Song
 
RHQ 공감 Seminar 6th
RHQ 공감 Seminar 6thRHQ 공감 Seminar 6th
RHQ 공감 Seminar 6th
Ted Won
 
막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)
연웅 조
 
11.react router dom
11.react router dom11.react router dom
11.react router dom
Daniel Lim
 
Vue.js 기초 실습.pptx
Vue.js 기초 실습.pptxVue.js 기초 실습.pptx
Vue.js 기초 실습.pptx
wonyong 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
 
Node.js and react
Node.js and reactNode.js and react
Node.js and react
HyungKuIm
 
Startup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSStartup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JS
Circulus
 
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.
 
Golang Project Guide from A to Z: From Feature Development to Enterprise Appl...
Golang Project Guide from A to Z: From Feature Development to Enterprise Appl...Golang Project Guide from A to Z: From Feature Development to Enterprise Appl...
Golang Project Guide from A to Z: From Feature Development to Enterprise Appl...
Kyuhyun Byun
 
20131217 html5
20131217 html520131217 html5
20131217 html5
DK Lee
 
7가지 동시성 모델 람다아키텍처
7가지 동시성 모델  람다아키텍처7가지 동시성 모델  람다아키텍처
7가지 동시성 모델 람다아키텍처
Sunggon Song
 
RHQ 공감 Seminar 6th
RHQ 공감 Seminar 6thRHQ 공감 Seminar 6th
RHQ 공감 Seminar 6th
Ted Won
 
막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)막하는스터디 두번째만남 Express(20151025)
막하는스터디 두번째만남 Express(20151025)
연웅 조
 
11.react router dom
11.react router dom11.react router dom
11.react router dom
Daniel Lim
 
Vue.js 기초 실습.pptx
Vue.js 기초 실습.pptxVue.js 기초 실습.pptx
Vue.js 기초 실습.pptx
wonyong 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
 
Ad

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

  • 1. CRA + SSR. (feat. TypeScript) 승형수 [email protected]
  • 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...)
  • 10. Implement 서버에서 한번: 1. ReactDOMServer.renderToString() 함수로 react app을 html string 으로 변환 2. 서버의 '^/$' route 에서 출력하게 함 클라이언트에서 또 한번: 3. 'build/index.html'에는 react 런타임 코 드와 페이지 코드가 붙어 있음! 4. 모든 리소스를 staDc 으로 서버에 제공하자
  • 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 제공
  • 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 구문 처리 플러그인
  • 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} ...`); }); });