SlideShare a Scribd company logo
Node.js server side
rendering
Kamil Płaczek
Jestem Kamil
JavaScript Developer
Server-side rendering
• Przeglądarka wykonuje zapytanie, serwer zwraca
przygotowany HTML
Node.js server-side rendering
Server-side rendering
• W kontekście SPA - uruchomienie kodu aplikacji i renderera
po stronie serwera (kod uniwersalny)
Node.js server-side rendering
Node.js server-side rendering
+
Node.js server-side rendering
src/client/index.js
import React from 'react';
import ReactDOM from ‘react-dom';
import {BrowserRouter} from 'react-router-dom';
import App from './app/app.component';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
początkowy kod - uruchomienie spa
src/server/index.js
import express from 'express';
const app = express();
app.use(express.static('dist'));
app.get('*', (req, res) => {
// doServerSideRenderingPls();
});
app.listen(3000);
początkowy kod - serwer
Problem 1: Routing
• Jak przełożyć routing z przeglądarki na serwer? 🤷
export default class App extends Component {
render() {
return (
<div className="container">
...
<Route exact path="/" component={Home} />
<Route path="/contact" component={Contact} />
</div>
</div>
);
}
}
Problem 1: Routing
• Routing jest uniwersalny.
export default class App extends Component {
render() {
return (
<div className="container">
...
<Route exact path="/" component={Home} />
<Route path="/contact" component={Contact} />
</div>
</div>
);
}
}
src/server/index.js
...
app.get('*', (req, res) => {
const context = {};
const appString = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
if (context.url) {
// redirect was done in client-side routing
res.redirect(301, context.url);
} else {
res.send(`
<!DOCTYPE html>
. . .
<body>
<div id="root">${appString}</div>
<script type="text/javascript" src="client.js"></script>
</body>
</html>
`);
}
});
...
server side rendering z routerem
src/server/index.js
...
app.get('*', (req, res) => {
const context = {};
const appString = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
if (context.url) {
// redirect was done in client-side routing
res.redirect(301, context.url);
} else {
res.send(`
<!DOCTYPE html>
. . .
<body>
<div id="root">${appString}</div>
<script type="text/javascript" src="client.js"></script>
</body>
</html>
`);
}
});
...
<div class="container" data-reactroot="">
<div>
<div class="navbar"><span class="brand">Taylor Swift</span>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<hr/>
<div>
<p>
Taylor Alison Swift (born December 13, 1989) is an
…
</p>
<ul></ul>
</div>
</div>
</div>
server side rendering z routerem
src/server/index.js
...
app.get('*', (req, res) => {
const context = {};
const appString = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
if (context.url) {
// redirect was done in client-side routing
res.redirect(301, context.url);
} else {
res.send(`
<!DOCTYPE html>
. . .
<body>
<div id="root">${appString}</div>
<script type="text/javascript" src="client.js"></script>
</body>
</html>
`);
}
});
...
server side rendering z routerem
<div class="container" data-reactroot="">
<div>
<div class="navbar"><span class="brand">Taylor Swift</span>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<hr/>
<div>
<p>
Taylor Alison Swift (born December 13, 1989) is an
…
</p>
<ul></ul>
</div>
</div>
</div>
src/server/index.js
...
app.get('*', (req, res) => {
const context = {};
const appString = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
if (context.url) {
// redirect was done in client-side routing
res.redirect(301, context.url);
} else {
res.send(`
<!DOCTYPE html>
. . .
<body>
<div id="root">${appString}</div>
<script type="text/javascript" src="client.js"></script>
</body>
</html>
`);
}
});
...
server side rendering z routerem
<div class="container" data-reactroot="">
<div>
<div class="navbar"><span class="brand">Taylor Swift</span>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<hr/>
<div>
<p>
Taylor Alison Swift (born December 13, 1989) is an
…
</p>
<ul></ul>
</div>
</div>
</div>
Problem 2: Dane
• Jak wypełnić dokument danymi podczas SSR? 🌅
async loadData() {
const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', {
headers: {Authorization: ‘Client-ID shAK3-it-0ff’},
});
...
this.setState({pics});
}
Problem 2: Dane
Server-side render
and return HTML
Fetch JS app code Run app client-side loadData()
Problem 2: Dane
Server-side render
and return HTML
Fetch JS app
code
Run app client-
side
loadData()loadData()
src/server/index.js
...
const getTaytayPics = async () => {
const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', {
headers: {Authorization: ‘Client-ID l00K-wH4t-Y0u-m4DE-m3-D0‘},
});
. . .
return pics;
};
...
app.get('*', async (req, res) => {
const context = {};
if (req.url === '/') {
context.pics = await getTaytayPics();
}
const appString = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
...
server side rendering + pobieranie danych v1
src/server/index.js
...
const getTaytayPics = async () => {
const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', {
headers: {Authorization: ‘Client-ID l00K-wH4t-Y0u-m4DE-m3-D0‘},
});
. . .
return pics;
};
...
app.get('*', async (req, res) => {
const context = {};
if (req.url === '/') {
context.pics = await getTaytayPics();
}
const appString = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
...
server side rendering + pobieranie danych v1
src/client/app/home/home.component.js
export class Home extends Component {
. . .
componentWillMount() {
if (this.props.staticContext && this.props.staticContext.pics) {
this.setState({pics: this.props.staticContext.pics})
} else {
this.loadData();
}
}
komponent react - wykorzystanie danych podczas ssr
Problem 2: Dane
Server-side render
and return HTML
Fetch JS app
code
Run app client-
side
loadData()loadData()
src/server/index.js
app.get('*', async (req, res) => {
. . .
if (req.url === '/') {
context.pics = await getTaytayPics();
}
. . .
} else {
res.send(`<!DOCTYPE html>
. . .>
<script>
window.APP_STATE = ${JSON.stringify({pics: context.pics})};
</script>
<script type="text/javascript" src="client.js"></script>
</body>
</html>`);
server side rendering - przekazanie informacji
src/client/app/home/home.component.js
componentWillMount() {
if (this.props.staticContext && this.props.staticContext.pics) {
this.setState({pics: this.props.staticContext.pics});
} else if (window && window.APP_STATE && window.APP_STATE.pics) {
this.setState({pics: window.APP_STATE.pics})
}
else {
this.loadData();
}
}
komponent react - wykorzystanie danych z ssr
src/server/index.js
...
const getTaytayPics = async () => {
const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', {
headers: {Authorization: ‘Client-ID l00K-wH4t-Y0u-m4DE-m3-D0‘},
});
. . .
return pics;
};
...
app.get('*', async (req, res) => {
const context = {};
if (req.url === '/') {
context.pics = await getTaytayPics();
}
const appString = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
...
DRY?
src/client/app/app.routes.js
import {Home} from './home/home.component';
import {Contact} from './contact/contact.component';
export const routes = [
{
component: Home,
path: '/',
exact: true,
},
{
component: Contact,
path: '/contact',
},
];
refactoring routingu
src/client/app/home/home.component.js
import fetch from ‘isomorphic-fetch';
. . .
static async loadData() {
const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', {
headers: {Authorization: 'Client-ID w1ld3est-dr3am5‘},
});
return pics;
}
. . .
}
refactoring komponentu react
src/server/index.js
import {StaticRouter, matchPath} from 'react-router';
import {routes} from '../client/app/app.routes';
. . .
app.get('*', async (req, res) => {
. . .
const matchedRoute = routes.find(route => matchPath(req.path, route));
if (matchedRoute) {
if (matchedRoute.component && matchedRoute.component.loadData) {
context.data = await matchedRoute.component.loadData();
}
} else {
return res.sendStatus(404);
}
const appString = renderToString(
. . .
refactoring pobierania danych na serwerze
Problem 2.5: Dane
• Jak wypełnić danymi store?
src/client/app/redux/taytay/taytay.actions.js
import fetch from 'isomorphic-fetch';
export const fetchPics = () => async dispatch => {
const res = await fetch('https://api.imgur.com/3/gallery/r/
taylorswift', {
headers: {Authorization: 'Client-ID 0447601918a7bb5'},
});
. . .
return dispatch(setPics(pics));
};
export const setPics = pics => ({. . .});
przeniesienie pobierania danych do akcji
src/client/app/home/home.component.js
import {fetchPics} from '../redux/taytay/taytay.actions';
export class Home extends Component {
static loadData = store => {
return store.dispatch(fetchPics());
};
. . .
wykorzystanie akcji w komponencie
src/client/index.js
import {createAppStore} from './create-store';
const store = createAppStore(window.APP_STATE || {});
ReactDOM.hydrate(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
stworzenie store + odtworzenie stanu z ssr
src/server/index.js
import {createAppStore} from '../client/create-store';
. . .
app.get('*', async (req, res) => {
const store = createAppStore({});
const matchedRoute = routes.find(route => matchPath(req.path, route));
if (matchedRoute) {
if (matchedRoute.component && matchedRoute.component.loadData) {
await matchedRoute.component.loadData(store);
}
. . .
} else {
const state = store.getState();
const html = `<!DOCTYPE html>
. . .
<div id="root">${appString}</div>
<script>
window.APP_STATE = ${JSON.stringify(state)};
</script>
. . .
}
});
stworzenie store + inicjalizacja stanu podczas ssr
src/server/index.js
import {createAppStore} from '../client/create-store';
. . .
app.get('*', async (req, res) => {
const store = createAppStore({});
const matchedRoute = routes.find(route => matchPath(req.path, route));
if (matchedRoute) {
if (matchedRoute.component && matchedRoute.component.loadData) {
await matchedRoute.component.loadData(store);
}
. . .
} else {
const state = store.getState();
const html = `<!DOCTYPE html>
. . .
<div id="root">${appString}</div>
<script>
window.APP_STATE = ${JSON.stringify(state)};
</script>
. . .
}
});
stworzenie store + inicjalizacja stanu podczas ssr
src/server/index.js
import {createAppStore} from '../client/create-store';
. . .
app.get('*', async (req, res) => {
const store = createAppStore({});
const matchedRoute = routes.find(route => matchPath(req.path, route));
if (matchedRoute) {
if (matchedRoute.component && matchedRoute.component.loadData) {
await matchedRoute.component.loadData(store);
}
. . .
} else {
const state = store.getState();
const html = `<!DOCTYPE html>
. . .
<div id="root">${appString}</div>
<script>
window.APP_STATE = ${JSON.stringify(state)};
</script>
. . .
}
});
stworzenie store + inicjalizacja stanu podczas ssr
Problem 3: Wydajność
• Jak szybko będzie działał serwer?
Problem 3: Wydajność
• Jak szybko będzie działał serwer?
src/server/index.js
import cache from 'memory-cache';
. . .
app.use('*', (req, res, next) => {
const cachedHtml = cache.get(req.originalUrl);
if (cachedHtml) {
res.send(cachedHtml);
} else {
next();
}
});
app.get('*', async (req, res) => {
. . .
cache.put(req.path, html);
res.send(html);
}
});
dodanie cache
Problem 3: Wydajność
src/server/index.js
import {createCacheStream} from ‘./cache-stream';
app.get('*', async (req, res) => {
. . .
const cacheStream = createCacheStream(req.path, cache);
cacheStream.pipe(res);
. . .
cacheStream.write(`<!DOCTYPE html>
<html>
. . .
<body>
<div id="root">`);
const appStream = renderToNodeStream(
. . .
);
appStream.pipe(cacheStream, {end: false});
appStream.on('end', () => {
cacheStream.end(`
. . .
</body>
</html>`);
});
}
});
wykorzystanie renderToNodeStream
src/server/cache-stream.js
import {Transform} from 'stream';
export const createCacheStream = (key, cache) => {
const bufferedChunks = [];
return new Transform({
transform(data, enc, cb) {
bufferedChunks.push(data);
cb(null, data);
},
flush(cb) {
cache.put(key, Buffer.concat(bufferedChunks).toString());
cb();
},
});
};
stream pomocniczy - cache
Problem 4: Uwierzytelnianie
• Jak renderować zawartość wymagającą autoryzacji? 🔐
Fetch token from the
server
Save token in
persistent storage
Pass token to the
server on requests
Authenticate &
authorize server-side
Problem 4: Uwierzytelnianie
app.post('/api/login', (req, res) => {
res.json({
token: '... ready for it?',
});
});
src/client/app/redux/auth/auth.actions.js
import fetch from 'isomorphic-fetch';
export const login = () => async dispatch => {
const res = await fetch(API_URL + '/api/login', {
method: 'POST',
});
const auth = await res.json();
localStorage.setItem('taytayAuth', auth.token);
return dispatch(setToken(auth.token));
};
akcja logowania
src/client/app/redux/auth/auth.actions.js
import fetch from 'isomorphic-fetch';
export const login = () => async dispatch => {
const res = await fetch(API_URL + '/api/login', {
method: 'POST',
});
const auth = await res.json();
localStorage.setItem('taytayAuth', auth.token);
return dispatch(setToken(auth.token));
};
akcja logowania
src/client/index.js
. . .
const token = localStorage.getItem('taytayAuth');
const store = createAppStore({
...(window.APP_STATE || {}),
auth: {
token,
},
});
ReactDOM.hydrate(
. . .
);
inicjalizacja store tokenem
src/client/app/redux/auth/auth.actions.js
const withAuthHoc = WrappedComponent => {
return class extends Component {
render() {
return this.props.isAuth ? (
<WrappedComponent {...this.props} />
) : (
<Redirect
to={{
pathname: '/login',
}}
/>
);
}
};
};
prosty guard na route
Problem 4: Uwierzytelnianie
• Jak renderować zawartość wymagającą autoryzacji? 🔐
🍪
src/client/app/redux/auth/auth.actions.js
import fetch from 'isomorphic-fetch';
export const login = () => async dispatch => {
const res = await fetch(API_URL + '/api/login', {
method: 'POST',
});
const auth = await res.json();
Cookies.set('taytayAuth', auth.token, {expires: 7, path: '/'});
return dispatch(setToken(auth.token));
};
zamiana localStorage na cookies
src/client/index.js
. . .
const token = Cookies.get('taytayAuth');
const store = createAppStore({
...(window.APP_STATE || {}),
auth: {
token,
},
});
ReactDOM.hydrate(
. . .
);
zamiana localStorage na cookies
src/server/index.js
import cookieParser from 'cookie-parser';
. . .
app.use(cookieParser());
. . .
app.get('*', async (req, res) => {
const token = req.cookies.taytayAuth;
const store = createAppStore({
auth: {
token,
},
});
if (matchedRoute) {
if (matchedRoute.private && !token) {
return res.redirect(301, ‘/login');
. . .
obsługa cookie na serwerze + inicjalizacja store
src/server/index.js
import cookieParser from 'cookie-parser';
. . .
app.use(cookieParser());
. . .
app.get('*', async (req, res) => {
const token = req.cookies.taytayAuth;
const store = createAppStore({
auth: {
token,
},
});
if (matchedRoute) {
if (matchedRoute.private && !token) {
return res.redirect(301, ‘/login');
. . .
obsługa cookie na serwerze + inicjalizacja store
Podsumowując
✅ Komunikacja z API
✅ Integracja z systemem zarządzania stanem
✅ Cache & streaming
✅ Uwierzytelnianie
Dzięki!
kamil.placzek@tsh.io

github.com/kamilplaczek/taytay-ssr

More Related Content

What's hot (20)

DOCX
How routing works in angular js
codeandyou forums
 
PDF
Workshop 13: AngularJS Parte II
Visual Engineering
 
PPT
Creating the interfaces of the future with the APIs of today
gerbille
 
PDF
Workshop 14: AngularJS Parte III
Visual Engineering
 
ODP
Pyramid REST
Łukasz Oleś
 
PDF
Introducing Rendr: Run your Backbone.js apps on the client and server
Spike Brehm
 
PPTX
Upgrading from Angular 1.x to Angular 2.x
Eyal Vardi
 
PDF
Workshop 12: AngularJS Parte I
Visual Engineering
 
PPTX
Rapid prototyping and easy testing with ember cli mirage
Krzysztof Bialek
 
PPTX
Extend sdk
Harsha Nagaraj
 
PPT
GHC
AidIQ
 
PPTX
Angular Tutorial Freshers and Experienced
rajkamaltibacademy
 
PDF
Specification-Driven Development of REST APIs by Alexander Zinchuk
OdessaJS Conf
 
PDF
Promises are so passé - Tim Perry - Codemotion Milan 2016
Codemotion
 
PDF
Heroku pop-behind-the-sense
Ben Lin
 
PDF
Rails 3 overview
Yehuda Katz
 
PPS
Implementing access control with zend framework
George Mihailov
 
PDF
实战Ecos
wanglei999
 
PPTX
How to Build SPA with Vue Router 2.0
Takuya Tejima
 
PPTX
The road to Ember.js 2.0
Codemotion
 
How routing works in angular js
codeandyou forums
 
Workshop 13: AngularJS Parte II
Visual Engineering
 
Creating the interfaces of the future with the APIs of today
gerbille
 
Workshop 14: AngularJS Parte III
Visual Engineering
 
Pyramid REST
Łukasz Oleś
 
Introducing Rendr: Run your Backbone.js apps on the client and server
Spike Brehm
 
Upgrading from Angular 1.x to Angular 2.x
Eyal Vardi
 
Workshop 12: AngularJS Parte I
Visual Engineering
 
Rapid prototyping and easy testing with ember cli mirage
Krzysztof Bialek
 
Extend sdk
Harsha Nagaraj
 
GHC
AidIQ
 
Angular Tutorial Freshers and Experienced
rajkamaltibacademy
 
Specification-Driven Development of REST APIs by Alexander Zinchuk
OdessaJS Conf
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Codemotion
 
Heroku pop-behind-the-sense
Ben Lin
 
Rails 3 overview
Yehuda Katz
 
Implementing access control with zend framework
George Mihailov
 
实战Ecos
wanglei999
 
How to Build SPA with Vue Router 2.0
Takuya Tejima
 
The road to Ember.js 2.0
Codemotion
 

Similar to Node.js server-side rendering (7)

PDF
Routing @ Scuk.cz
Jakub Kulhan
 
PDF
Building Universal Web Apps with React ForwardJS 2017
Elyse Kolker Gordon
 
PDF
React Router: React Meetup XXL
Rob Gietema
 
PDF
Universal JavaScript
名辰 洪
 
PDF
NextJS - Online Summit for Frontend Developers September 2020
Milad Heydari
 
PPTX
React JS CONCEPT AND DETAILED EXPLANATION
harshavardhanjuttika
 
Routing @ Scuk.cz
Jakub Kulhan
 
Building Universal Web Apps with React ForwardJS 2017
Elyse Kolker Gordon
 
React Router: React Meetup XXL
Rob Gietema
 
Universal JavaScript
名辰 洪
 
NextJS - Online Summit for Frontend Developers September 2020
Milad Heydari
 
React JS CONCEPT AND DETAILED EXPLANATION
harshavardhanjuttika
 
Ad

More from The Software House (20)

PDF
Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
The Software House
 
PDF
Uszanowanko Podsumowanko
The Software House
 
PDF
Jak efektywnie podejść do certyfikacji w AWS?
The Software House
 
PDF
O co chodzi z tą dostępnością cyfrową?
The Software House
 
PDF
Chat tekstowy z użyciem Amazon Chime
The Software House
 
PDF
Migracje danych serverless
The Software House
 
PDF
Jak nie zwariować z architekturą Serverless?
The Software House
 
PDF
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
The Software House
 
PDF
Feature flags na ratunek projektu w JavaScript
The Software House
 
PDF
Typowanie nominalne w TypeScript
The Software House
 
PDF
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
The Software House
 
PDF
Serverless Compose vs hurtownia danych
The Software House
 
PDF
Testy API: połączenie z bazą danych czy implementacja w pamięci
The Software House
 
PDF
Jak skutecznie read model. Case study
The Software House
 
PDF
Firestore czyli ognista baza od giganta z Doliny Krzemowej
The Software House
 
PDF
Jak utrzymać stado Lambd w ryzach
The Software House
 
PDF
Jak poskromić AWS?
The Software House
 
PDF
O łączeniu Storyblok i Next.js
The Software House
 
PDF
Amazon Step Functions. Sposób na implementację procesów w chmurze
The Software House
 
PDF
Od Figmy do gotowej aplikacji bez linijki kodu
The Software House
 
Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
The Software House
 
Uszanowanko Podsumowanko
The Software House
 
Jak efektywnie podejść do certyfikacji w AWS?
The Software House
 
O co chodzi z tą dostępnością cyfrową?
The Software House
 
Chat tekstowy z użyciem Amazon Chime
The Software House
 
Migracje danych serverless
The Software House
 
Jak nie zwariować z architekturą Serverless?
The Software House
 
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
The Software House
 
Feature flags na ratunek projektu w JavaScript
The Software House
 
Typowanie nominalne w TypeScript
The Software House
 
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
The Software House
 
Serverless Compose vs hurtownia danych
The Software House
 
Testy API: połączenie z bazą danych czy implementacja w pamięci
The Software House
 
Jak skutecznie read model. Case study
The Software House
 
Firestore czyli ognista baza od giganta z Doliny Krzemowej
The Software House
 
Jak utrzymać stado Lambd w ryzach
The Software House
 
Jak poskromić AWS?
The Software House
 
O łączeniu Storyblok i Next.js
The Software House
 
Amazon Step Functions. Sposób na implementację procesów w chmurze
The Software House
 
Od Figmy do gotowej aplikacji bez linijki kodu
The Software House
 
Ad

Recently uploaded (20)

PPTX
L1A Season 1 ENGLISH made by A hegy fixed
toszolder91
 
PDF
Build Fast, Scale Faster: Milvus vs. Zilliz Cloud for Production-Ready AI
Zilliz
 
PPT
introduction to networking with basics coverage
RamananMuthukrishnan
 
PPT
introductio to computers by arthur janry
RamananMuthukrishnan
 
PDF
The-Hidden-Dangers-of-Skipping-Penetration-Testing.pdf.pdf
naksh4thra
 
PPTX
法国巴黎第二大学本科毕业证{Paris 2学费发票Paris 2成绩单}办理方法
Taqyea
 
DOCX
Custom vs. Off-the-Shelf Banking Software
KristenCarter35
 
PPTX
PE introd.pptxfrgfgfdgfdgfgrtretrt44t444
nepmithibai2024
 
PPTX
sajflsajfljsdfljslfjslfsdfas;fdsfksadfjlsdflkjslgfs;lfjlsajfl;sajfasfd.pptx
theknightme
 
PPTX
ONLINE BIRTH CERTIFICATE APPLICATION SYSYTEM PPT.pptx
ShyamasreeDutta
 
PPT
Agilent Optoelectronic Solutions for Mobile Application
andreashenniger2
 
PPT
Computer Securityyyyyyyy - Chapter 2.ppt
SolomonSB
 
PPTX
一比一原版(LaTech毕业证)路易斯安那理工大学毕业证如何办理
Taqyea
 
PPTX
L1A Season 1 Guide made by A hegy Eng Grammar fixed
toszolder91
 
PDF
Azure_DevOps introduction for CI/CD and Agile
henrymails
 
PPTX
Lec15_Mutability Immutability-converted.pptx
khanjahanzaib1
 
PPTX
西班牙武康大学毕业证书{UCAMOfferUCAM成绩单水印}原版制作
Taqyea
 
PDF
𝐁𝐔𝐊𝐓𝐈 𝐊𝐄𝐌𝐄𝐍𝐀𝐍𝐆𝐀𝐍 𝐊𝐈𝐏𝐄𝐑𝟒𝐃 𝐇𝐀𝐑𝐈 𝐈𝐍𝐈 𝟐𝟎𝟐𝟓
hokimamad0
 
PDF
Apple_Environmental_Progress_Report_2025.pdf
yiukwong
 
PPT
Computer Securityyyyyyyy - Chapter 1.ppt
SolomonSB
 
L1A Season 1 ENGLISH made by A hegy fixed
toszolder91
 
Build Fast, Scale Faster: Milvus vs. Zilliz Cloud for Production-Ready AI
Zilliz
 
introduction to networking with basics coverage
RamananMuthukrishnan
 
introductio to computers by arthur janry
RamananMuthukrishnan
 
The-Hidden-Dangers-of-Skipping-Penetration-Testing.pdf.pdf
naksh4thra
 
法国巴黎第二大学本科毕业证{Paris 2学费发票Paris 2成绩单}办理方法
Taqyea
 
Custom vs. Off-the-Shelf Banking Software
KristenCarter35
 
PE introd.pptxfrgfgfdgfdgfgrtretrt44t444
nepmithibai2024
 
sajflsajfljsdfljslfjslfsdfas;fdsfksadfjlsdflkjslgfs;lfjlsajfl;sajfasfd.pptx
theknightme
 
ONLINE BIRTH CERTIFICATE APPLICATION SYSYTEM PPT.pptx
ShyamasreeDutta
 
Agilent Optoelectronic Solutions for Mobile Application
andreashenniger2
 
Computer Securityyyyyyyy - Chapter 2.ppt
SolomonSB
 
一比一原版(LaTech毕业证)路易斯安那理工大学毕业证如何办理
Taqyea
 
L1A Season 1 Guide made by A hegy Eng Grammar fixed
toszolder91
 
Azure_DevOps introduction for CI/CD and Agile
henrymails
 
Lec15_Mutability Immutability-converted.pptx
khanjahanzaib1
 
西班牙武康大学毕业证书{UCAMOfferUCAM成绩单水印}原版制作
Taqyea
 
𝐁𝐔𝐊𝐓𝐈 𝐊𝐄𝐌𝐄𝐍𝐀𝐍𝐆𝐀𝐍 𝐊𝐈𝐏𝐄𝐑𝟒𝐃 𝐇𝐀𝐑𝐈 𝐈𝐍𝐈 𝟐𝟎𝟐𝟓
hokimamad0
 
Apple_Environmental_Progress_Report_2025.pdf
yiukwong
 
Computer Securityyyyyyyy - Chapter 1.ppt
SolomonSB
 

Node.js server-side rendering

  • 3. Server-side rendering • Przeglądarka wykonuje zapytanie, serwer zwraca przygotowany HTML
  • 5. Server-side rendering • W kontekście SPA - uruchomienie kodu aplikacji i renderera po stronie serwera (kod uniwersalny)
  • 8. +
  • 10. src/client/index.js import React from 'react'; import ReactDOM from ‘react-dom'; import {BrowserRouter} from 'react-router-dom'; import App from './app/app.component'; ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById('root') ); początkowy kod - uruchomienie spa
  • 11. src/server/index.js import express from 'express'; const app = express(); app.use(express.static('dist')); app.get('*', (req, res) => { // doServerSideRenderingPls(); }); app.listen(3000); początkowy kod - serwer
  • 12. Problem 1: Routing • Jak przełożyć routing z przeglądarki na serwer? 🤷 export default class App extends Component { render() { return ( <div className="container"> ... <Route exact path="/" component={Home} /> <Route path="/contact" component={Contact} /> </div> </div> ); } }
  • 13. Problem 1: Routing • Routing jest uniwersalny. export default class App extends Component { render() { return ( <div className="container"> ... <Route exact path="/" component={Home} /> <Route path="/contact" component={Contact} /> </div> </div> ); } }
  • 14. src/server/index.js ... app.get('*', (req, res) => { const context = {}; const appString = renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); if (context.url) { // redirect was done in client-side routing res.redirect(301, context.url); } else { res.send(` <!DOCTYPE html> . . . <body> <div id="root">${appString}</div> <script type="text/javascript" src="client.js"></script> </body> </html> `); } }); ... server side rendering z routerem
  • 15. src/server/index.js ... app.get('*', (req, res) => { const context = {}; const appString = renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); if (context.url) { // redirect was done in client-side routing res.redirect(301, context.url); } else { res.send(` <!DOCTYPE html> . . . <body> <div id="root">${appString}</div> <script type="text/javascript" src="client.js"></script> </body> </html> `); } }); ... <div class="container" data-reactroot=""> <div> <div class="navbar"><span class="brand">Taylor Swift</span> <ul> <li><a href="/">Home</a></li> <li><a href="/contact">Contact</a></li> </ul> </div> <hr/> <div> <p> Taylor Alison Swift (born December 13, 1989) is an … </p> <ul></ul> </div> </div> </div> server side rendering z routerem
  • 16. src/server/index.js ... app.get('*', (req, res) => { const context = {}; const appString = renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); if (context.url) { // redirect was done in client-side routing res.redirect(301, context.url); } else { res.send(` <!DOCTYPE html> . . . <body> <div id="root">${appString}</div> <script type="text/javascript" src="client.js"></script> </body> </html> `); } }); ... server side rendering z routerem <div class="container" data-reactroot=""> <div> <div class="navbar"><span class="brand">Taylor Swift</span> <ul> <li><a href="/">Home</a></li> <li><a href="/contact">Contact</a></li> </ul> </div> <hr/> <div> <p> Taylor Alison Swift (born December 13, 1989) is an … </p> <ul></ul> </div> </div> </div>
  • 17. src/server/index.js ... app.get('*', (req, res) => { const context = {}; const appString = renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); if (context.url) { // redirect was done in client-side routing res.redirect(301, context.url); } else { res.send(` <!DOCTYPE html> . . . <body> <div id="root">${appString}</div> <script type="text/javascript" src="client.js"></script> </body> </html> `); } }); ... server side rendering z routerem <div class="container" data-reactroot=""> <div> <div class="navbar"><span class="brand">Taylor Swift</span> <ul> <li><a href="/">Home</a></li> <li><a href="/contact">Contact</a></li> </ul> </div> <hr/> <div> <p> Taylor Alison Swift (born December 13, 1989) is an … </p> <ul></ul> </div> </div> </div>
  • 18. Problem 2: Dane • Jak wypełnić dokument danymi podczas SSR? 🌅 async loadData() { const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', { headers: {Authorization: ‘Client-ID shAK3-it-0ff’}, }); ... this.setState({pics}); }
  • 19. Problem 2: Dane Server-side render and return HTML Fetch JS app code Run app client-side loadData()
  • 20. Problem 2: Dane Server-side render and return HTML Fetch JS app code Run app client- side loadData()loadData()
  • 21. src/server/index.js ... const getTaytayPics = async () => { const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', { headers: {Authorization: ‘Client-ID l00K-wH4t-Y0u-m4DE-m3-D0‘}, }); . . . return pics; }; ... app.get('*', async (req, res) => { const context = {}; if (req.url === '/') { context.pics = await getTaytayPics(); } const appString = renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); ... server side rendering + pobieranie danych v1
  • 22. src/server/index.js ... const getTaytayPics = async () => { const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', { headers: {Authorization: ‘Client-ID l00K-wH4t-Y0u-m4DE-m3-D0‘}, }); . . . return pics; }; ... app.get('*', async (req, res) => { const context = {}; if (req.url === '/') { context.pics = await getTaytayPics(); } const appString = renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); ... server side rendering + pobieranie danych v1
  • 23. src/client/app/home/home.component.js export class Home extends Component { . . . componentWillMount() { if (this.props.staticContext && this.props.staticContext.pics) { this.setState({pics: this.props.staticContext.pics}) } else { this.loadData(); } } komponent react - wykorzystanie danych podczas ssr
  • 24. Problem 2: Dane Server-side render and return HTML Fetch JS app code Run app client- side loadData()loadData()
  • 25. src/server/index.js app.get('*', async (req, res) => { . . . if (req.url === '/') { context.pics = await getTaytayPics(); } . . . } else { res.send(`<!DOCTYPE html> . . .> <script> window.APP_STATE = ${JSON.stringify({pics: context.pics})}; </script> <script type="text/javascript" src="client.js"></script> </body> </html>`); server side rendering - przekazanie informacji
  • 26. src/client/app/home/home.component.js componentWillMount() { if (this.props.staticContext && this.props.staticContext.pics) { this.setState({pics: this.props.staticContext.pics}); } else if (window && window.APP_STATE && window.APP_STATE.pics) { this.setState({pics: window.APP_STATE.pics}) } else { this.loadData(); } } komponent react - wykorzystanie danych z ssr
  • 27. src/server/index.js ... const getTaytayPics = async () => { const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', { headers: {Authorization: ‘Client-ID l00K-wH4t-Y0u-m4DE-m3-D0‘}, }); . . . return pics; }; ... app.get('*', async (req, res) => { const context = {}; if (req.url === '/') { context.pics = await getTaytayPics(); } const appString = renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); ... DRY?
  • 28. src/client/app/app.routes.js import {Home} from './home/home.component'; import {Contact} from './contact/contact.component'; export const routes = [ { component: Home, path: '/', exact: true, }, { component: Contact, path: '/contact', }, ]; refactoring routingu
  • 29. src/client/app/home/home.component.js import fetch from ‘isomorphic-fetch'; . . . static async loadData() { const res = await fetch('https://api.imgur.com/3/gallery/r/taylorswift', { headers: {Authorization: 'Client-ID w1ld3est-dr3am5‘}, }); return pics; } . . . } refactoring komponentu react
  • 30. src/server/index.js import {StaticRouter, matchPath} from 'react-router'; import {routes} from '../client/app/app.routes'; . . . app.get('*', async (req, res) => { . . . const matchedRoute = routes.find(route => matchPath(req.path, route)); if (matchedRoute) { if (matchedRoute.component && matchedRoute.component.loadData) { context.data = await matchedRoute.component.loadData(); } } else { return res.sendStatus(404); } const appString = renderToString( . . . refactoring pobierania danych na serwerze
  • 31. Problem 2.5: Dane • Jak wypełnić danymi store?
  • 32. src/client/app/redux/taytay/taytay.actions.js import fetch from 'isomorphic-fetch'; export const fetchPics = () => async dispatch => { const res = await fetch('https://api.imgur.com/3/gallery/r/ taylorswift', { headers: {Authorization: 'Client-ID 0447601918a7bb5'}, }); . . . return dispatch(setPics(pics)); }; export const setPics = pics => ({. . .}); przeniesienie pobierania danych do akcji
  • 33. src/client/app/home/home.component.js import {fetchPics} from '../redux/taytay/taytay.actions'; export class Home extends Component { static loadData = store => { return store.dispatch(fetchPics()); }; . . . wykorzystanie akcji w komponencie
  • 34. src/client/index.js import {createAppStore} from './create-store'; const store = createAppStore(window.APP_STATE || {}); ReactDOM.hydrate( <Provider store={store}> <BrowserRouter> <App /> </BrowserRouter> </Provider>, document.getElementById('root') ); stworzenie store + odtworzenie stanu z ssr
  • 35. src/server/index.js import {createAppStore} from '../client/create-store'; . . . app.get('*', async (req, res) => { const store = createAppStore({}); const matchedRoute = routes.find(route => matchPath(req.path, route)); if (matchedRoute) { if (matchedRoute.component && matchedRoute.component.loadData) { await matchedRoute.component.loadData(store); } . . . } else { const state = store.getState(); const html = `<!DOCTYPE html> . . . <div id="root">${appString}</div> <script> window.APP_STATE = ${JSON.stringify(state)}; </script> . . . } }); stworzenie store + inicjalizacja stanu podczas ssr
  • 36. src/server/index.js import {createAppStore} from '../client/create-store'; . . . app.get('*', async (req, res) => { const store = createAppStore({}); const matchedRoute = routes.find(route => matchPath(req.path, route)); if (matchedRoute) { if (matchedRoute.component && matchedRoute.component.loadData) { await matchedRoute.component.loadData(store); } . . . } else { const state = store.getState(); const html = `<!DOCTYPE html> . . . <div id="root">${appString}</div> <script> window.APP_STATE = ${JSON.stringify(state)}; </script> . . . } }); stworzenie store + inicjalizacja stanu podczas ssr
  • 37. src/server/index.js import {createAppStore} from '../client/create-store'; . . . app.get('*', async (req, res) => { const store = createAppStore({}); const matchedRoute = routes.find(route => matchPath(req.path, route)); if (matchedRoute) { if (matchedRoute.component && matchedRoute.component.loadData) { await matchedRoute.component.loadData(store); } . . . } else { const state = store.getState(); const html = `<!DOCTYPE html> . . . <div id="root">${appString}</div> <script> window.APP_STATE = ${JSON.stringify(state)}; </script> . . . } }); stworzenie store + inicjalizacja stanu podczas ssr
  • 38. Problem 3: Wydajność • Jak szybko będzie działał serwer?
  • 39. Problem 3: Wydajność • Jak szybko będzie działał serwer?
  • 40. src/server/index.js import cache from 'memory-cache'; . . . app.use('*', (req, res, next) => { const cachedHtml = cache.get(req.originalUrl); if (cachedHtml) { res.send(cachedHtml); } else { next(); } }); app.get('*', async (req, res) => { . . . cache.put(req.path, html); res.send(html); } }); dodanie cache
  • 42. src/server/index.js import {createCacheStream} from ‘./cache-stream'; app.get('*', async (req, res) => { . . . const cacheStream = createCacheStream(req.path, cache); cacheStream.pipe(res); . . . cacheStream.write(`<!DOCTYPE html> <html> . . . <body> <div id="root">`); const appStream = renderToNodeStream( . . . ); appStream.pipe(cacheStream, {end: false}); appStream.on('end', () => { cacheStream.end(` . . . </body> </html>`); }); } }); wykorzystanie renderToNodeStream
  • 43. src/server/cache-stream.js import {Transform} from 'stream'; export const createCacheStream = (key, cache) => { const bufferedChunks = []; return new Transform({ transform(data, enc, cb) { bufferedChunks.push(data); cb(null, data); }, flush(cb) { cache.put(key, Buffer.concat(bufferedChunks).toString()); cb(); }, }); }; stream pomocniczy - cache
  • 44. Problem 4: Uwierzytelnianie • Jak renderować zawartość wymagającą autoryzacji? 🔐 Fetch token from the server Save token in persistent storage Pass token to the server on requests Authenticate & authorize server-side
  • 45. Problem 4: Uwierzytelnianie app.post('/api/login', (req, res) => { res.json({ token: '... ready for it?', }); });
  • 46. src/client/app/redux/auth/auth.actions.js import fetch from 'isomorphic-fetch'; export const login = () => async dispatch => { const res = await fetch(API_URL + '/api/login', { method: 'POST', }); const auth = await res.json(); localStorage.setItem('taytayAuth', auth.token); return dispatch(setToken(auth.token)); }; akcja logowania
  • 47. src/client/app/redux/auth/auth.actions.js import fetch from 'isomorphic-fetch'; export const login = () => async dispatch => { const res = await fetch(API_URL + '/api/login', { method: 'POST', }); const auth = await res.json(); localStorage.setItem('taytayAuth', auth.token); return dispatch(setToken(auth.token)); }; akcja logowania
  • 48. src/client/index.js . . . const token = localStorage.getItem('taytayAuth'); const store = createAppStore({ ...(window.APP_STATE || {}), auth: { token, }, }); ReactDOM.hydrate( . . . ); inicjalizacja store tokenem
  • 49. src/client/app/redux/auth/auth.actions.js const withAuthHoc = WrappedComponent => { return class extends Component { render() { return this.props.isAuth ? ( <WrappedComponent {...this.props} /> ) : ( <Redirect to={{ pathname: '/login', }} /> ); } }; }; prosty guard na route
  • 50. Problem 4: Uwierzytelnianie • Jak renderować zawartość wymagającą autoryzacji? 🔐 🍪
  • 51. src/client/app/redux/auth/auth.actions.js import fetch from 'isomorphic-fetch'; export const login = () => async dispatch => { const res = await fetch(API_URL + '/api/login', { method: 'POST', }); const auth = await res.json(); Cookies.set('taytayAuth', auth.token, {expires: 7, path: '/'}); return dispatch(setToken(auth.token)); }; zamiana localStorage na cookies
  • 52. src/client/index.js . . . const token = Cookies.get('taytayAuth'); const store = createAppStore({ ...(window.APP_STATE || {}), auth: { token, }, }); ReactDOM.hydrate( . . . ); zamiana localStorage na cookies
  • 53. src/server/index.js import cookieParser from 'cookie-parser'; . . . app.use(cookieParser()); . . . app.get('*', async (req, res) => { const token = req.cookies.taytayAuth; const store = createAppStore({ auth: { token, }, }); if (matchedRoute) { if (matchedRoute.private && !token) { return res.redirect(301, ‘/login'); . . . obsługa cookie na serwerze + inicjalizacja store
  • 54. src/server/index.js import cookieParser from 'cookie-parser'; . . . app.use(cookieParser()); . . . app.get('*', async (req, res) => { const token = req.cookies.taytayAuth; const store = createAppStore({ auth: { token, }, }); if (matchedRoute) { if (matchedRoute.private && !token) { return res.redirect(301, ‘/login'); . . . obsługa cookie na serwerze + inicjalizacja store
  • 55. Podsumowując ✅ Komunikacja z API ✅ Integracja z systemem zarządzania stanem ✅ Cache & streaming ✅ Uwierzytelnianie