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

netlify.toml # lolly page requests will be rendered and persisted on demand [[redirects]] from = “/lolly/*$” to = “/.netlify/functions/showLolly” status = 200

@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