Skip to content

Commit 9dba65b

Browse files
committed
SSR: request intereception optimization
1 parent 15ec9f0 commit 9dba65b

File tree

2 files changed

+69
-19
lines changed

2 files changed

+69
-19
lines changed

public/app.js

+25-13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ dbHelper.setApp(firebase.initializeApp(shared.firebaseConfig));
2323

2424
let _posts = [];
2525
let _filteringBy = null;
26+
const FILTERING_PARAMS = ['domain', 'author'];
2627

2728
async function fetchPosts(url, maxResults = null) {
2829
try {
@@ -56,31 +57,39 @@ function handleDelete(el, dateStr, url) {
5657
}
5758

5859
function filterBy(key, needle = null) {
60+
if (key && !FILTERING_PARAMS.includes(key)) {
61+
return;
62+
}
63+
5964
const currentURL = new URL(location.href);
65+
const params = currentURL.searchParams;
6066
const filterEl = document.querySelector('#filtering');
6167
const needleEl = filterEl.querySelector('.filtering-needle');
62-
let posts = _posts;
6368

64-
// TODO: this clears all params...even those unrelated to filtering.
65-
for (const key of currentURL.searchParams.keys()) {
66-
currentURL.searchParams.delete(key);
69+
// Clear all previous filters.
70+
for (const key of params.keys()) {
71+
if (FILTERING_PARAMS.includes(key)) {
72+
params.delete(key);
73+
}
6774
}
6875

76+
let filteredPosts = _posts;
77+
6978
// TODO: support filtering on more than one thing.
7079
if (needle === _filteringBy) {
71-
currentURL.searchParams.delete(key);
80+
params.delete(key);
7281
filterEl.classList.remove('on');
7382
_filteringBy = null;
7483
} else {
75-
posts = posts.filter(post => post[key] === needle);
76-
currentURL.searchParams.set(key, needle);
84+
filteredPosts = filteredPosts.filter(post => post[key] === needle);
85+
params.set(key, needle);
7786
needleEl.textContent = needle;
7887
filterEl.classList.add('on');
7988
_filteringBy = needle;
8089
}
8190

8291
window.history.pushState(null, '', currentURL.href);
83-
renderPosts(posts, container);
92+
renderPosts(filteredPosts, container);
8493
}
8594

8695
function clearFilters() {
@@ -176,17 +185,20 @@ async function getLatestPosts() {
176185
}
177186

178187
(async() => {
179-
const ssr = container.querySelector('#posts');
188+
const PRE_RENDERED = container.querySelector('#posts'); // Already exists in DOM if we've SSR.
180189

181190
try {
182-
_posts = await getLatestPosts(); // populate cache
191+
// Populates client-side cache for future realtime updates.
192+
// Note: this basically results in 2x requests per page load, as we're
193+
// making the same requests the server just made. Now repeating them client-side.
194+
_posts = await getLatestPosts();
183195

184-
// Posts are already rendered in the DOM for the SSR case. Don't re-render.
185-
if (!ssr) {
196+
// Posts markup is already in place if we're SSRing. Don't re-render DOM.
197+
if (!PRE_RENDERED) {
186198
renderPosts(_posts, container);
187199
}
188200

189-
// Subscribe to firestore updates.
201+
// Subscribe to realtime firestore updates.
190202
realtimeUpdatePosts(util.currentYear);
191203

192204
const params = new URL(location.href).searchParams;

server.mjs

+44-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import fs from 'fs';
2020
import bodyParser from 'body-parser';
21+
import url from 'url';
22+
const URL = url.URL;
2123
// import compression from 'compression';
2224
// import minify from 'express-minify';
2325
import express from 'express';
@@ -42,21 +44,56 @@ function updateRSSFeedsDaily() {
4244
setTimeout(updateRSSFeedsDaily, dayInMilliseconds);
4345
}
4446

45-
async function ssr(url) {
47+
/**
48+
*
49+
* @param url {string} URL to prerender.
50+
* @param onlyCriticalRequests {boolean} Reduces the number of requests the
51+
* browser makes by aborting requests that are non-critical to rendering
52+
* the DOM of the page (stylesheets, images, media). True by default.
53+
* @return {string} Serialized page output as an html string.
54+
*/
55+
async function ssr(url, onlyCriticalRequests = true) {
4656
if (RENDER_CACHE.has(url)) {
4757
return RENDER_CACHE.get(url);
4858
}
4959

60+
// Add param so client-side page can know it's being rendered by headless on the server.
61+
const urlToFetch = new URL(url);
62+
urlToFetch.searchParams.set('headless', '');
63+
5064
const tic = Date.now();
5165
const browser = await puppeteer.launch({
52-
args: ['--disable-dev-shm-usage']
66+
args: ['--disable-dev-shm-usage'],
5367
});
5468
const page = await browser.newPage();
55-
await page.goto(url, {waitUntil: 'domcontentloaded'});
69+
70+
// Small optimization. Since we only care about rendered DOM, ignore images,
71+
// css, and other media that don't produce markup.
72+
if (onlyCriticalRequests) {
73+
await page.setRequestInterception(true);
74+
page.on('request', req => {
75+
const whitelist = ['document', 'script', 'xhr', 'fetch', 'websocket'];
76+
if (whitelist.includes(req.resourceType())) {
77+
req.continue();
78+
} else {
79+
req.abort();
80+
// req.respond({
81+
// status: 200,
82+
// contentType: 'text/plain',
83+
// body: ''
84+
// });
85+
}
86+
});
87+
}
88+
89+
// TODO: another optimization may be to take enter page out of rendering pipeline.
90+
// Add html { display: none } to page.
91+
92+
await page.goto(urlToFetch.href, {waitUntil: 'domcontentloaded'});
5693
await page.waitForSelector('#posts'); // wait for posts to be in filled in page.
57-
const html = await page.content(); // Browser "SSR" page for us! Get serialized DOM.
94+
const html = await page.content(); // Use browser to prerender page, get serialized DOM output!
5895
await browser.close();
59-
console.info(`Headless chrome render time: ${Date.now() - tic}ms`);
96+
console.info(`Headless rendered page in: ${Date.now() - tic}ms`);
6097

6198
RENDER_CACHE.set(url, html); // cache rendered page.
6299

@@ -99,7 +136,8 @@ app.use(express.static('node_modules/lit-html'));
99136
// });
100137

101138
app.get('/ssr', async (req, res) => {
102-
const html = await ssr(req.getOrigin());
139+
const optimizeReqs = 'noreduce' in req.query ? false : true;
140+
const html = await ssr(req.getOrigin(), optimizeReqs);
103141
res.status(200).send(html);
104142
});
105143

0 commit comments

Comments
 (0)