SlideShare una empresa de Scribd logo
1 de 56
Descargar para leer sin conexión
Node.js server side
rendering
Kamil Płaczek
Jestem Kamil
JavaScript Developer
Server-side rendering
• Przeglądarka wykonuje zapytanie, serwer zwraca
przygotowany HTML
Server-side rendering
• W kontekście SPA - uruchomienie kodu aplikacji i renderera
po stronie serwera (kod uniwersalny)
+
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

Más contenido relacionado

La actualidad más candente

How routing works in angular js
How routing works in angular jsHow routing works in angular js
How routing works in angular jscodeandyou forums
 
Workshop 13: AngularJS Parte II
Workshop 13: AngularJS Parte IIWorkshop 13: AngularJS Parte II
Workshop 13: AngularJS Parte IIVisual Engineering
 
Creating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of todayCreating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of todaygerbille
 
Workshop 14: AngularJS Parte III
Workshop 14: AngularJS Parte IIIWorkshop 14: AngularJS Parte III
Workshop 14: AngularJS Parte IIIVisual Engineering
 
Introducing Rendr: Run your Backbone.js apps on the client and server
Introducing Rendr: Run your Backbone.js apps on the client and serverIntroducing Rendr: Run your Backbone.js apps on the client and server
Introducing Rendr: Run your Backbone.js apps on the client and serverSpike Brehm
 
Upgrading from Angular 1.x to Angular 2.x
Upgrading from Angular 1.x to Angular 2.xUpgrading from Angular 1.x to Angular 2.x
Upgrading from Angular 1.x to Angular 2.xEyal Vardi
 
Workshop 12: AngularJS Parte I
Workshop 12: AngularJS Parte IWorkshop 12: AngularJS Parte I
Workshop 12: AngularJS Parte IVisual Engineering
 
Rapid prototyping and easy testing with ember cli mirage
Rapid prototyping and easy testing with ember cli mirageRapid prototyping and easy testing with ember cli mirage
Rapid prototyping and easy testing with ember cli mirageKrzysztof Bialek
 
Angular Tutorial Freshers and Experienced
Angular Tutorial Freshers and ExperiencedAngular Tutorial Freshers and Experienced
Angular Tutorial Freshers and Experiencedrajkamaltibacademy
 
Specification-Driven Development of REST APIs by Alexander Zinchuk
Specification-Driven Development of REST APIs by Alexander Zinchuk   Specification-Driven Development of REST APIs by Alexander Zinchuk
Specification-Driven Development of REST APIs by Alexander Zinchuk OdessaJS Conf
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Codemotion
 
Heroku pop-behind-the-sense
Heroku pop-behind-the-senseHeroku pop-behind-the-sense
Heroku pop-behind-the-senseBen Lin
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overviewYehuda Katz
 
Implementing access control with zend framework
Implementing access control with zend frameworkImplementing access control with zend framework
Implementing access control with zend frameworkGeorge Mihailov
 
How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0Takuya Tejima
 
The road to Ember.js 2.0
The road to Ember.js 2.0The road to Ember.js 2.0
The road to Ember.js 2.0Codemotion
 

La actualidad más candente (20)

How routing works in angular js
How routing works in angular jsHow routing works in angular js
How routing works in angular js
 
Workshop 13: AngularJS Parte II
Workshop 13: AngularJS Parte IIWorkshop 13: AngularJS Parte II
Workshop 13: AngularJS Parte II
 
Creating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of todayCreating the interfaces of the future with the APIs of today
Creating the interfaces of the future with the APIs of today
 
Workshop 14: AngularJS Parte III
Workshop 14: AngularJS Parte IIIWorkshop 14: AngularJS Parte III
Workshop 14: AngularJS Parte III
 
Pyramid REST
Pyramid RESTPyramid REST
Pyramid REST
 
Introducing Rendr: Run your Backbone.js apps on the client and server
Introducing Rendr: Run your Backbone.js apps on the client and serverIntroducing Rendr: Run your Backbone.js apps on the client and server
Introducing Rendr: Run your Backbone.js apps on the client and server
 
Upgrading from Angular 1.x to Angular 2.x
Upgrading from Angular 1.x to Angular 2.xUpgrading from Angular 1.x to Angular 2.x
Upgrading from Angular 1.x to Angular 2.x
 
Workshop 12: AngularJS Parte I
Workshop 12: AngularJS Parte IWorkshop 12: AngularJS Parte I
Workshop 12: AngularJS Parte I
 
Rapid prototyping and easy testing with ember cli mirage
Rapid prototyping and easy testing with ember cli mirageRapid prototyping and easy testing with ember cli mirage
Rapid prototyping and easy testing with ember cli mirage
 
Extend sdk
Extend sdkExtend sdk
Extend sdk
 
GHC
GHCGHC
GHC
 
Angular Tutorial Freshers and Experienced
Angular Tutorial Freshers and ExperiencedAngular Tutorial Freshers and Experienced
Angular Tutorial Freshers and Experienced
 
Specification-Driven Development of REST APIs by Alexander Zinchuk
Specification-Driven Development of REST APIs by Alexander Zinchuk   Specification-Driven Development of REST APIs by Alexander Zinchuk
Specification-Driven Development of REST APIs by Alexander Zinchuk
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016
 
Heroku pop-behind-the-sense
Heroku pop-behind-the-senseHeroku pop-behind-the-sense
Heroku pop-behind-the-sense
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
Implementing access control with zend framework
Implementing access control with zend frameworkImplementing access control with zend framework
Implementing access control with zend framework
 
实战Ecos
实战Ecos实战Ecos
实战Ecos
 
How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0How to Build SPA with Vue Router 2.0
How to Build SPA with Vue Router 2.0
 
The road to Ember.js 2.0
The road to Ember.js 2.0The road to Ember.js 2.0
The road to Ember.js 2.0
 

Similar a Node.js server-side rendering

Angular server side rendering - Strategies & Technics
Angular server side rendering - Strategies & Technics Angular server side rendering - Strategies & Technics
Angular server side rendering - Strategies & Technics Eliran Eliassy
 
Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...
Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...
Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...OdessaJS Conf
 
Building Web Apps with Express
Building Web Apps with ExpressBuilding Web Apps with Express
Building Web Apps with ExpressAaron Stannard
 
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)Igor Bronovskyy
 
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...Codemotion
 
Angular 1.x vs. Angular 2.x
Angular 1.x vs. Angular 2.xAngular 1.x vs. Angular 2.x
Angular 1.x vs. Angular 2.xEyal Vardi
 
Node.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community VijayawadaNode.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community VijayawadaLuciano Mammino
 
Universal JavaScript
Universal JavaScriptUniversal JavaScript
Universal JavaScript名辰 洪
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Elyse Kolker Gordon
 
Restful Web Service
Restful Web ServiceRestful Web Service
Restful Web ServiceBin Cai
 
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 202010 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020Matt Raible
 
Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN...
Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN...Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN...
Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN...Fwdays
 
Dart Power Tools
Dart Power ToolsDart Power Tools
Dart Power ToolsMatt Norris
 
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0Frost
 
Aurelia Meetup Paris
Aurelia Meetup ParisAurelia Meetup Paris
Aurelia Meetup ParisAhmed Radjdi
 

Similar a Node.js server-side rendering (20)

Angular server side rendering - Strategies & Technics
Angular server side rendering - Strategies & Technics Angular server side rendering - Strategies & Technics
Angular server side rendering - Strategies & Technics
 
Reduxing like a pro
Reduxing like a proReduxing like a pro
Reduxing like a pro
 
Express JS
Express JSExpress JS
Express JS
 
Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...
Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...
Maciej Treder ''Angular Universal - a medicine for the Angular + SEO/CDN issu...
 
Building Web Apps with Express
Building Web Apps with ExpressBuilding Web Apps with Express
Building Web Apps with Express
 
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
09 - express nodes on the right angle - vitaliy basyuk - it event 2013 (5)
 
React native by example by Vadim Ruban
React native by example by Vadim RubanReact native by example by Vadim Ruban
React native by example by Vadim Ruban
 
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
Universal JavaScript Web Applications with React - Luciano Mammino - Codemoti...
 
Silex Cheat Sheet
Silex Cheat SheetSilex Cheat Sheet
Silex Cheat Sheet
 
Angular 1.x vs. Angular 2.x
Angular 1.x vs. Angular 2.xAngular 1.x vs. Angular 2.x
Angular 1.x vs. Angular 2.x
 
Node.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community VijayawadaNode.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community Vijayawada
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
 
Universal JavaScript
Universal JavaScriptUniversal JavaScript
Universal JavaScript
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017
 
Restful Web Service
Restful Web ServiceRestful Web Service
Restful Web Service
 
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 202010 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
10 Excellent Ways to Secure Spring Boot Applications - Okta Webinar 2020
 
Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN...
Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN...Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN...
Maciej Treder "Server-side rendering with Angular—be faster and more SEO, CDN...
 
Dart Power Tools
Dart Power ToolsDart Power Tools
Dart Power Tools
 
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
Building an angular application -1 ( API: Golang, Database: Postgres) v1.0
 
Aurelia Meetup Paris
Aurelia Meetup ParisAurelia Meetup Paris
Aurelia Meetup Paris
 

Más de The Software House

Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...The Software House
 
Jak efektywnie podejść do certyfikacji w AWS?
Jak efektywnie podejść do certyfikacji w AWS?Jak efektywnie podejść do certyfikacji w AWS?
Jak efektywnie podejść do certyfikacji w AWS?The Software House
 
O co chodzi z tą dostępnością cyfrową?
O co chodzi z tą dostępnością cyfrową?O co chodzi z tą dostępnością cyfrową?
O co chodzi z tą dostępnością cyfrową?The Software House
 
Chat tekstowy z użyciem Amazon Chime
Chat tekstowy z użyciem Amazon ChimeChat tekstowy z użyciem Amazon Chime
Chat tekstowy z użyciem Amazon ChimeThe Software House
 
Jak nie zwariować z architekturą Serverless?
Jak nie zwariować z architekturą Serverless?Jak nie zwariować z architekturą Serverless?
Jak nie zwariować z architekturą Serverless?The Software House
 
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWSAnaliza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWSThe Software House
 
Feature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScriptFeature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScriptThe Software House
 
Typowanie nominalne w TypeScript
Typowanie nominalne w TypeScriptTypowanie nominalne w TypeScript
Typowanie nominalne w TypeScriptThe Software House
 
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQLAutomatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQLThe Software House
 
Serverless Compose vs hurtownia danych
Serverless Compose vs hurtownia danychServerless Compose vs hurtownia danych
Serverless Compose vs hurtownia danychThe Software House
 
Testy API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięciTesty API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięciThe Software House
 
Jak skutecznie read model. Case study
Jak skutecznie read model. Case studyJak skutecznie read model. Case study
Jak skutecznie read model. Case studyThe Software House
 
Firestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny KrzemowejFirestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny KrzemowejThe Software House
 
Jak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzachJak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzachThe Software House
 
O łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.jsO łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.jsThe Software House
 
Amazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurzeAmazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurzeThe Software House
 
Od Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki koduOd Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki koduThe Software House
 

Más de The Software House (20)

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

Último

Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night StandHot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Standkumarajju5765
 
Call Girls In Pratap Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Pratap Nagar Delhi 💯Call Us 🔝8264348440🔝Call Girls In Pratap Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Pratap Nagar Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
Networking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGNetworking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGAPNIC
 
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.soniya singh
 
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call GirlVIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girladitipandeya
 
Radiant Call girls in Dubai O56338O268 Dubai Call girls
Radiant Call girls in Dubai O56338O268 Dubai Call girlsRadiant Call girls in Dubai O56338O268 Dubai Call girls
Radiant Call girls in Dubai O56338O268 Dubai Call girlsstephieert
 
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine ServiceHot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Servicesexy call girls service in goa
 
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...aditipandeya
 
horny (9316020077 ) Goa Call Girls Service by VIP Call Girls in Goa
horny (9316020077 ) Goa  Call Girls Service by VIP Call Girls in Goahorny (9316020077 ) Goa  Call Girls Service by VIP Call Girls in Goa
horny (9316020077 ) Goa Call Girls Service by VIP Call Girls in Goasexy call girls service in goa
 
How is AI changing journalism? (v. April 2024)
How is AI changing journalism? (v. April 2024)How is AI changing journalism? (v. April 2024)
How is AI changing journalism? (v. April 2024)Damian Radcliffe
 
Challengers I Told Ya ShirtChallengers I Told Ya Shirt
Challengers I Told Ya ShirtChallengers I Told Ya ShirtChallengers I Told Ya ShirtChallengers I Told Ya Shirt
Challengers I Told Ya ShirtChallengers I Told Ya Shirtrahman018755
 
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...Neha Pandey
 
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark Web
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark WebGDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark Web
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark WebJames Anderson
 
Delhi Call Girls Rohini 9711199171 ☎✔👌✔ Whatsapp Hard And Sexy Vip Call
Delhi Call Girls Rohini 9711199171 ☎✔👌✔ Whatsapp Hard And Sexy Vip CallDelhi Call Girls Rohini 9711199171 ☎✔👌✔ Whatsapp Hard And Sexy Vip Call
Delhi Call Girls Rohini 9711199171 ☎✔👌✔ Whatsapp Hard And Sexy Vip Callshivangimorya083
 
Call Now ☎ 8264348440 !! Call Girls in Shahpur Jat Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Shahpur Jat Escort Service Delhi N.C.R.Call Now ☎ 8264348440 !! Call Girls in Shahpur Jat Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Shahpur Jat Escort Service Delhi N.C.R.soniya singh
 
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607dollysharma2066
 
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...SofiyaSharma5
 

Último (20)

Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night StandHot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
Hot Call Girls |Delhi |Hauz Khas ☎ 9711199171 Book Your One night Stand
 
Call Girls In Pratap Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Pratap Nagar Delhi 💯Call Us 🔝8264348440🔝Call Girls In Pratap Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Pratap Nagar Delhi 💯Call Us 🔝8264348440🔝
 
Networking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGNetworking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOG
 
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Green Park Escort Service Delhi N.C.R.
 
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call GirlVIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
VIP 7001035870 Find & Meet Hyderabad Call Girls LB Nagar high-profile Call Girl
 
Radiant Call girls in Dubai O56338O268 Dubai Call girls
Radiant Call girls in Dubai O56338O268 Dubai Call girlsRadiant Call girls in Dubai O56338O268 Dubai Call girls
Radiant Call girls in Dubai O56338O268 Dubai Call girls
 
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No AdvanceRohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
 
Call Girls In Noida 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
Call Girls In Noida 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICECall Girls In Noida 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
Call Girls In Noida 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
 
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine ServiceHot Service (+9316020077 ) Goa  Call Girls Real Photos and Genuine Service
Hot Service (+9316020077 ) Goa Call Girls Real Photos and Genuine Service
 
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
 
horny (9316020077 ) Goa Call Girls Service by VIP Call Girls in Goa
horny (9316020077 ) Goa  Call Girls Service by VIP Call Girls in Goahorny (9316020077 ) Goa  Call Girls Service by VIP Call Girls in Goa
horny (9316020077 ) Goa Call Girls Service by VIP Call Girls in Goa
 
How is AI changing journalism? (v. April 2024)
How is AI changing journalism? (v. April 2024)How is AI changing journalism? (v. April 2024)
How is AI changing journalism? (v. April 2024)
 
Challengers I Told Ya ShirtChallengers I Told Ya Shirt
Challengers I Told Ya ShirtChallengers I Told Ya ShirtChallengers I Told Ya ShirtChallengers I Told Ya Shirt
Challengers I Told Ya ShirtChallengers I Told Ya Shirt
 
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...
𓀤Call On 7877925207 𓀤 Ahmedguda Call Girls Hot Model With Sexy Bhabi Ready Fo...
 
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Ashram Chowk Delhi 💯Call Us 🔝8264348440🔝
 
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark Web
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark WebGDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark Web
GDG Cloud Southlake 32: Kyle Hettinger: Demystifying the Dark Web
 
Delhi Call Girls Rohini 9711199171 ☎✔👌✔ Whatsapp Hard And Sexy Vip Call
Delhi Call Girls Rohini 9711199171 ☎✔👌✔ Whatsapp Hard And Sexy Vip CallDelhi Call Girls Rohini 9711199171 ☎✔👌✔ Whatsapp Hard And Sexy Vip Call
Delhi Call Girls Rohini 9711199171 ☎✔👌✔ Whatsapp Hard And Sexy Vip Call
 
Call Now ☎ 8264348440 !! Call Girls in Shahpur Jat Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Shahpur Jat Escort Service Delhi N.C.R.Call Now ☎ 8264348440 !! Call Girls in Shahpur Jat Escort Service Delhi N.C.R.
Call Now ☎ 8264348440 !! Call Girls in Shahpur Jat Escort Service Delhi N.C.R.
 
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
 
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
 

Node.js server-side rendering

  • 3. Server-side rendering • Przeglądarka wykonuje zapytanie, serwer zwraca przygotowany HTML
  • 4.
  • 5. Server-side rendering • W kontekście SPA - uruchomienie kodu aplikacji i renderera po stronie serwera (kod uniwersalny)
  • 6.
  • 7.
  • 8. +
  • 9.
  • 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