A presentation at MMT Tech Meetup by Phil Hawksworth
Rendering models that scale Whatever your framework Phil Hawksworth Director of Developer Experience, Netlify
Finding the right approach for a project
Mo clients, mo problems (Mo clients, mo architectures) @philhawksworth unsplash.com/photos/Bli_WDC2oU0
Web development is not Highlander
Finding the simplest solution
Finding the simplest solution (implementation, cost, support, confidence, robustness)
Jamstack @philhawksworth
Jamstack A way of thinking about how to build for the web. The UI is compiled, the frontend is decoupled, and data is pulled in as needed. @philhawksworth
from Generating responses to every request on demand @philhawksworth
to Generating responses to requests in advance @philhawksworth
to Generating responses to requests in advance ( You know… like we did in the 90s ) @philhawksworth
BUZZW ORD BIN Pre-rendering @philhawksworth GO
? N E H W Build time @philhawksworth VS Request time
Front-end code is no longer limited to being a product of a back-end system @philhawksworth
? E R E WH Application server @philhawksworth VS Serverless functions B UZZW O RD B INGO
Front-end code is no longer limited to being a product of a back-end system @philhawksworth
…I love these discussions!
@ Phil Hawksworth Director of Developer Experience, Netlify
findthat.at/jamstack/book
findthat.at/interesting
Let’s talk 1 2 Benefits and limits Breaking the ceiling 3 Removing the ceiling
1 Benefits and limits @philhawksworth
Security Scale Speed How susceptible our infrastructure is to attack How well we can handle high volumes of traffic How quickly we can deliver content to the users @philhawksworth Workflow Developer e xperie nce Sperience How efficiently and confidently we can build, release, and maintain sites
Benefit Simplicity @philhawksworth
TRADITIONAL STACK LOAD BALANCERS WEB SERVERS DATABASES JAMSTACK ASSETS ON CDN @philhawksworth BUILD CODE & CONTENT
TRADITIONAL STACK LOAD BALANCERS WEB SERVERS DATABASES JAMSTACK ASSETS ON CDN BUILD AHEAD OF TIME @philhawksworth CODE & CONTENT
Benefit Immutable, Atomic deploys @philhawksworth
Live Deploy 88 Deploy 87 Deploy 86 @philhawksworth Deploy 86
Deploy 89 Live Deploy 88 Deploy 87 Deploy 86 @philhawksworth Deploy 86
Live Deploy 89 Deploy 88 Deploy 87 Deploy 86 @philhawksworth Deploy 86
Deploy 89 Live Deploy 88 Deploy 87 Deploy 86 @philhawksworth Deploy 86
Deploy 89 Deploy 88 Deploy 87 Live Deploy 86 @philhawksworth Deploy 86
Deploys are immutable and atomic @philhawksworth
JAMSTACK ASSETS ON CDN BUILD AHEAD OF TIME @philhawksworth CODE & CONTENT
Challenge Very large sites @philhawksworth
Challenge User generated content @philhawksworth
We reach a ceiling @philhawksworth unsplash.com/photos/unIuWzRFaSM
2 Breaking the ceiling @philhawksworth
vlolly.net findthat.at/lollytricks @philhawksworth
Thousands of unique pages and user-generated content @philhawksworth
New content rendered on-demand as a fallback @philhawksworth
New content rendered on-demand as a fallback @philhawksworth
New content rendered on-demand as a fallback @philhawksworth
@philhawksworth
vlolly.net/lolly/abc7A7VxP @philhawksworth
New content Rebuild and fill the gaps 404 Render page on demand @philhawksworth
functions/showLolly.js const faunadb = require(‘faunadb’); const pageTemplate = require(‘./lollyTemplate.js’); // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); // Create a handler function to export const handler = async(event) =>” { // get the lolly ID from the request let lollyId = event.path.split(“lolly/”)[1]; // find the lolly data in the DB client.query( q.Get(q.Match(q.Index(“lolly_by_path”), lollyId)) ).then((response) =>” { // if found, return a view return { statusCode: 200, headers: { “Content-Type”: “text/html”, }, body: pageTemplate(response.data) } }).catch((error) =>” { // not found or an error, send to the generic error page }); } // export the handler function exports.handler = handler; @philhawksworth const faunadb = require(‘faunadb’); const pageTemplate = require(‘./lollyTemplate.js’);
functions/showLolly.js const faunadb = require(‘faunadb’); const pageTemplate = require(‘./lollyTemplate.js’); // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); // Create a handler function to export const handler = async(event) =>” { // get the lolly ID from the request let lollyId = event.path.split(“lolly/”)[1]; // find the lolly data in the DB client.query( q.Get(q.Match(q.Index(“lolly_by_path”), lollyId)) ).then((response) =>” { // if found, return a view return { statusCode: 200, headers: { “Content-Type”: “text/html”, }, body: pageTemplate(response.data) } }).catch((error) =>” { // not found or an error, send to the generic error page }); } // export the handler function exports.handler = handler; @philhawksworth // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET });
functions/showLolly.js const faunadb = require(‘faunadb’); const pageTemplate = require(‘./lollyTemplate.js’); // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); // Create a handler function to export const handler = async(event) =>” { // get the lolly ID from the request let lollyId = event.path.split(“lolly/”)[1]; // find the lolly data in the DB client.query( q.Get(q.Match(q.Index(“lolly_by_path”), lollyId)) ).then((response) =>” { // if found, return a view return { statusCode: 200, headers: { “Content-Type”: “text/html”, }, body: pageTemplate(response.data) } }).catch((error) =>” { // not found or an error, send to the generic error page }); } // export the handler function exports.handler = handler; @philhawksworth // Create a handler function to export const handler = async(event) =>” { // get the lolly ID from the request let lollyId = event.path.split(“lolly/”)[1];
functions/showLolly.js const faunadb = require(‘faunadb’); const pageTemplate = require(‘./lollyTemplate.js’); // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); // Create a handler function to export const handler = async(event) =>” { // get the lolly ID from the request let lollyId = event.path.split(“lolly/”)[1]; // find the lolly data in the DB client.query( q.Get(q.Match(q.Index(“lolly_by_path”), lollyId)) ).then((response) =>” { // if found, return a view return { statusCode: 200, headers: { “Content-Type”: “text/html”, }, body: pageTemplate(response.data) } }).catch((error) =>” { // not found or an error, send to the generic error page }); } // export the handler function exports.handler = handler; @philhawksworth // find the lolly data in the DB client.query( q.Get(q.Match(q.Index(“lolly_by_path”), lollyId)) ).then((response) =>” {
functions/showLolly.js const faunadb = require(‘faunadb’); const pageTemplate = require(‘./lollyTemplate.js’); // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); // Create a handler function to export const handler = async(event) =>” { // get the lolly ID from the request let lollyId = event.path.split(“lolly/”)[1]; // find the lolly data in the DB client.query( q.Get(q.Match(q.Index(“lolly_by_path”), lollyId)) ).then((response) =>” { // if found, return a view return { statusCode: 200, headers: { “Content-Type”: “text/html”, }, body: pageTemplate(response.data) } }).catch((error) =>” { // not found or an error, send to the generic error page }); } // export the handler function exports.handler = handler; @philhawksworth // if found, return a view return { statusCode: 200, headers: { “Content-Type”: “text/html”, }, body: pageTemplate(response.data) }
It’s JavaScript Add this to any framework or site @philhawksworth
@philhawksworth
vlolly.net/lolly/abc7A7VxP @philhawksworth
New content Rebuild and fill the gaps 404 Render page on demand Lots of pages Cache the builds @philhawksworth
vlolly.net/popsicle/abc7A7VxP @philhawksworth
( ) Making-fun-of-myenglish-accent-driven development @philhawksworth
It’s JavaScript Add this to any framework or site @philhawksworth
But… More pages means longer builds @philhawksworth We could have better cache characteristics Complexity starting to muddy the mental model I’m inventing things rather than using a formal pattern
But behold, there are now Numerous rendering approaches @philhawksworth
ISR @philhawksworth DSG DPR
3 Removing the ceiling @philhawksworth
ISR @philhawksworth DSG DPR
Without undoing the benefits of Jamstack @philhawksworth
@philhawksworth
@philhawksworth
Without migrating to another framework @philhawksworth
DPR Distributed Persistent Rendering @philhawksworth
DPR Distributed Persistent Rendering @philhawksworth
@philhawksworth
vlolly.net/lolly/abc7A7VxP @philhawksworth
functions/showLolly.js const faunadb = require(‘faunadb’); const pageTemplate = require(‘./lollyTemplate.js’); const { builder } = require(‘@netlify/functions’); // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); // Create a handler function to export const handler = async(event) =>” { // get the lolly ID from the request let lollyId = event.path.split(“lolly/”)[1]; // find the lolly data in the DB client.query( q.Get(q.Match(q.Index(“lolly_by_path”), lollyId)) ).then((response) =>” { // if found, return a view return { statusCode: 200, headers: { “Content-Type”: “text/html”, }, body: pageTemplate(response.data) } }).catch((error) =>” { // not found or an error, send to the generic error page }); } // export the handler function exports.handler = builder(handler); @philhawksworth const { builder } = require(‘@netlify/functions’);
functions/showLolly.js const faunadb = require(‘faunadb’); const pageTemplate = require(‘./lollyTemplate.js’); const { builder } = require(‘@netlify/functions’); // setup and auth the Fauna DB client const q = faunadb.query; const client = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET }); // Create a handler function to export const handler = async(event) =>” { // get the lolly ID from the request let lollyId = event.path.split(“lolly/”)[1]; // find the lolly data in the DB client.query( q.Get(q.Match(q.Index(“lolly_by_path”), lollyId)) ).then((response) =>” { // if found, return a view return { statusCode: 200, headers: { “Content-Type”: “text/html”, }, body: pageTemplate(response.data) } }).catch((error) =>” { // not found or an error, send to the generic error page }); } // export the handler function exports.handler = builder(handler); @philhawksworth // export the handler function exports.handler = builder(handler);
Here endeth the refactoring @philhawksworth
Generated whenever we deploy @philhawksworth Added to the deploy when first requested
New content Render into cache on demand 404 Nope. Page delivered from deploy Lots of pages Fewer pages built in deploys @philhawksworth
tl;dr: I deleted some stuff and then added 2 lines @philhawksworth
This additive approach uses on-demand builders @philhawksworth
It can be augment a site built with any framework @philhawksworth
It doesn’t compromise the core benefits of Jamstack @philhawksworth
Next? @philhawksworth
Links Run Gatsby 4 with DSG and SSR on Netlify https://www.netlify.com/blog/2021/09/16/run-gatsby-4-with-dsg-and-ssr-on-netlify-today/ Keeping things fresh with stale-while-revalidate https://web.dev/stale-while-revalidate/ Use Next.js 12 on Netlify https://www.netlify.com/blog/2021/10/27/use-next.js-12-on-netlify/ Eleventy Serverless https://www.11ty.dev/docs/plugins/serverless/ Distributed Persistent Rendering: A new Jamstack approach for faster builds https://findthat.at/dpr Distributed Persistent Rendering RFC https://findthat.at/dpr/rfc
Thank you @ philhawksworth
How can we extend the scale and scope of our sites with different rendering models. And can we do that without compromising the benefits we get from the Jamstack or needing to migrate to a specific framework?