Distributed Persistent Rendering
with Jason Lengstorf
In this solo episode, Jason will work through building an app with serverless functions using distributed persistent rendering.
Resources & Links
JASON LENGSTORF: Hello, everyone. And, welcome to another episode of Learn With Jason. I just realized I didn't turn my lights on. Let me set the mood lights. Hold on one second...beautiful. I love it. All right. It's good to see y'all. I'm rolling solo today. I'm super-excited to hang out with you. We're going to do a build -- an app build. I'm going to mess around with some new technology, super-excited. First, I want to hear from you, chat? How have you been? How's life? You're immediately coming at me with a submarine.
Good, I should have expected. I should have known. I'm excited. It's been awhile since we've done a solo. Thank you, yes. I'm really glad to hear that things are going well. But, yeah, I'm just having a blast these days. I got to take a vacation. Did you all have fun with Ben? I thought Ben did a great job. Haven't touched code in two weeks. You're great, Nikki. That is excellent. When I took my vacation, I, like -- the first couple of days, I kept checking in on stuff, then I got yelled out, which I really appreciated. Someone was like, you need to be on vacation. I say that all the time, and I'm not doing it right now. I should do that. I followed their advice. I disconnected and took some time to be actually on vacation and hung out on the coast. Sat in a hot tub. It was wonderful.
Yeah, Ben really did do a great job. I think that would be very, very fun. Hey, how's the sub?
(Laughter). Yeah. It's exactly that. No hot tub time machine, unfortunately. That would have been way more fun.
Thank you for the sub, Felix. That is very much appreciated. I'm very much onboard. Alexis got to take a vacation? Yes! Yes. Taking that time. Being gone. Ben, thank you for being in the chat today. What a great day.
Here's what I want to do today, I want to build an app using a new pattern -- well, okay, I'm going to say "new pattern," but I'm going to explain why it's not new. But what I want to talk about, today, is what is called distributed persistent rendering. One of the things -- thank you for the "subscribe." Very much appreciated.
So, what I'm excited about DPR for -- and I'm going to use DPR throughout. If you hear me say DPR -- I'm saying this like people joining late are going to hear me say this. DPR, Distributed Persistent Rendering. I cannot say it over and over again without getting tongue-tied. DPR is a really interesting model because one of the things that makes the Jamstack powerful -- you know, the Jamstack, in general, is something I'm very excited about. I joined Netlify because I believe in the Jamstack architecture. I'm working on a product called enterpriseproduct.com. I'll send a little linky, in case you want to check that out. It's a thing that I care about a lot. I spend a lot of time thinking about Jamstack. And, the reason for that is because I think that it helps simplify mental models. Thank you, all. Ben is gifting subs all over the place. Thank you, all, for being here. Make sure you spam the heck out of that Boop emote. We spend a lot of time talking about the Boop emote, but there are some good ones out there.
So, let's talk about the Jamstack a little bit. What I like about it is it simplifies the mental model. You start working on apps at any scale, the complexity starts to get out of control. You start to end up in the situation where you are -- you're building an app, but the thing that you're doing is -- thank you for the sub. The thing that you are doing is you are, you're working on the frontend, but your frontend has state. It has animations. It has, you know, different view port sizes. It's got to be responsive. It's got to be accessible. That doesn't go away. As apps get more complex, they start to -- now you're thinking about, okay, how do we deploy this thing? So, when you deploy it, you have a server, maybe? And if you have a server, then you start to get traffic and if you get a lot of traffic, then you have to worry about "am I getting so much traffic that my server can't handle it?" Now you're starting to think about scale. How do I do load-balancers and caching? It starts to move you further and further away from being a frontend developer.
I was at IBM and I was hired as a frontend developer, but my job including managing a Docker container and the Kubernetes deployment of our frontend. I see -- wait, is Cassidy even here or is this -- oh, my goodness! Oh, my goodness! Chat, operating a full Boop overload without Cassidy Williams. Ben, you have ascended, look at you go! Beautiful! Beautiful!
Okay. So, that complexity is a lot. You know, I -- as a developer, I want to be working on the thing that I was hired to do. I'm a frontend developer.
(Laughter). You're not going to take me seriously anymore. Should I just stand here and get above the boops? I'll try to be up here and be serious.
Um, you almost crashed it. Don't you crash my overlay. Yeah. Just standing on my tippy toes so you can all see me. But -- but so if we talk about, you know, being a frontend developer, I want to get things to production as quickly as I can and when I get pulled out of my expertise of building frontends and into this complexity of dealing with things that aren't my expertise, I'm not a specialist in DevOps, I'm not a backend engineer, I'm not trained to scale and keep a server up and running. Because of the complexities of frontend, we are seeing ourselves doing more and more of that work or we find ourselves in situations where there's a frontend team and a backend team and an Ops team and to get something live is days -- or even weeks -- to get the change we made to get deployed because it has to go through so many processes and checks. Like, we want that security. We want that confidence that the thing we're deploying is good. But if we remove a lot of that complexity, then we can have that confidence and we can be fast and that's where I think the Jamstack is exciting.
When you've got a team that's working on the Jamstack, you've removed the need for managing those servers. You've removed the need for dealing with that scale and you're instead able to build a frontend and then deploy it to a CDN, which automatically handles scale, the caching and things you would need for performance. And instead, you can focus on building the best, most accessible, most performant frontend. So, I like that. When you use an architecture that shrinks your responsibilities without taking away any functionality, you're allowing yourself to move faster with more confidence and less risk.
Because of the way the Jamstack is set up, you get this ability to deploy the thing and after you've deployed it, if something goes wrong, you can just roll back to the previous version because each build of the site is its own independent folder and that is -- that is really powerful. You know. Thank you -- oh, boy, we got all subs all over the place. Thank you.
Booping takes brain power. It does take brain power. Yeah. Excellent. Thank you, all, very much. Very, very excited about this. I see you all got some hype train emotes, too.
We've got the Jamstack is removing complexity. It's making it easier for people to work, but now when you go to CDNs, you have static assets and you don't have server. You can do a lot on the client side, but you can't, for example, keep a database -- [barking]. There's our stampede.
[Music playing in the background].
But, so, to overcome that problem, then, you start looking at serverless and serverless is great for this because serverless has all the things that I like about the Jamstack. You do the thing that you're there to do. You know, you write some logic. You make an API call. You hit a database and none of the things you don't want to do. You're not writing server boilerplate, you are not having to set up the routing logic or, you know, an API gateway or any of those things you would need to get a server live. You write it, commit it, push it and it goes live. I'm talking about the Netlify flow. Your set gets in Git -- we're going to talk about that later.
We want to talk about distributed persistent rendering. They execute every time you call them. That's not a problem in terms of scaling because every time you call a serverless function, they can handle millions of hits. The COVID-19 tracking project when to -- it was two million API requests overnight. And so, it's a very powerful model, but because you're making that request every time, it can overload an API. It can get you rate-limited. It can add a lot of, like, bandwidth and overhead and serverless functions are a little bit slower than hitting a fully-deployed server that's got a load-balancer across the load.
To fix that, that's where distributed persistent rendering comes in. The idea is that you have a serverless function, but if you hit that serverless function, you can cache the result of it and keep it as if it were on the CDN. And, you know, this is something that's in early phases. It's in RFC. And, you know what I will do is, how about this, I'll do the switch-over. Let's get over to the solo stream view. And, I'm going to do a quick shout-out. We have live captioning, like we do with every show. And that is available on the home page of learnwithjason.dev. That's made possible by white folk, thank you so much, White Coat, for being here today. That comes through the support of our sponsors, Netlify, Fauna, Auth0. It helps me keep the lights on and keep everything running.
That being said, let's look at this RFC. And, let's talk a little bit about -- I talked about the challenge. We want to be able to do dynamic things with Jamstack sites. We don't want to lose any capabilities. We just want to remove that complexity. We want to de-risk and deploy to production multiple times a day without having that stress of having to do six jobs. We want to be frontend developers, who can deploy, on our own, with safety and with speed. So, our goal is to overcome that challenge with serverless functions because if serverless functions run every time, it adds some tricky bits, right.
But we don't want to introduce a server because a problem with the server, you start to introduce layers of caching and layers of caching are hard. There's a joke about two hard things, in computer science, are caching and naming things. That joke is valid for a reason. It is extremely hard to get caching right because if you have a simple layer of caching, you have a file and you cache it and when that file changes, you invalidate the cache. That is fine. Where it starts to get tricky is when you've got an API and that API has a caching layer and you've got static assets and those have a caching layer and a server hitting the API and that has a caching layer. You might end up with -- kind of, like, the old version of the API call, but the new version of the server. Those are broken and you have to go clear the cache all over again. There starts to become this complexity.
A way I think about this is in factorial math. If you have one thing, like, let's do a factorial calculator, because this is -- this is, like, mind-blowing math. If you have, like, a one possibility and you do the factorial, which -- where is it? Can I just, like, type? So, if you do factorial, which is the exclamation point of "1," it is 1. If I clear that and do the factorial of "2," it's 2. If I clear that and I do the factorial of "3," it's 6. Okay. All right. That escalated kind of quickly. If we take the factorial of "4," it's 24. Going fast. 5, 120. Oh, jeez. Okay. 6, 720. Now that's a lot of possibilities. And, 7. 5,040. The number of possibilities is compounding here. You get to 9 and -- so, 362, 000 possible combinations if you get into the factorial of 9. So when you start adding -- it is brutally-complex to try to manage multiple moving parts. If you get above two or three moving parts, we lose the ability to hold it in our heads and that is the part that is really, really challenging. If you've got a system that has 10 moving parts. If you think about caching layers, proxy layer, a server layer. This is three million, 3.6 million possibilities of combinations of just 10 moving parts. So, the ability for human beings to keep complexity in their head, it is very hard-capped at a low number. Right. And that is really, really important to keep in mind when you're building systems. If you have a combination of systems and each of those have to be managed individually, it is limited. If you've got 10 moving parts, you're hosed. There's no way that I'm keeping more than about seven things in my head at any given time, which if we use seven as our cap, we've got about three moving parts that we can manage, right? That's how many things that fit into my mental model. Roughly three things before I fall apart.
So, this is kind of where I have been thinking about this, from the Jamstack capacity, because when you do this, as an architecture, you've got your frontend -- and there are a lot of things that move in a frontend. The way your frontend gets to the internet, when you introduce a server, you're adding a lot of moving parts, which means you start adding teams or complexities or checks and then you start looking at the DevOps solutions.
Then, if you look at moving this to, like, the -- you know, the Netlify model where you write your code, you commit it to Git and it goes live, I can keep that in my head. I don't need a whiteboard to map this out. I can look at this and say, okay, I know how this works because the frontend is here, I know what the build command is. If the build command succeeds, then we deploy that. Serverless is the same way. You write a serverless function, you commit it to Git and a serverless function can take input and output. You can track the deployment process for that. It's very -- it's trackable. You can keep that in your head.
When you start talking about wanting to add additional layers of caching, things quickly become untrackable. If you think about the output of something being immutable, it's easy to keep in mind. You have a thing and if you hit that asset, you'll get the same thing every time. If I hit a URL and I get different results based on when I hit it, caching starts to become really complicated. If I can hit the thing and after five minutes, it'll expire, that can be really, really challenging. And, if -- what I found is, when caching gets challenging, people don't learn it. They learn how to work around it. So, an example of this is when I was on a frontend team at IBM, we had really robust caching strategies that were really complex and everybody just added random number generators to the end of their URLs to intentionally break the cache every time someone hit it. So, that complexity doesn't lead to people following the rules, that leads to people finding workarounds that break the rules and that ends up very costly and it's [Indiscernible] the system. Right.
What you tried to do was make things safe and what you ended up with was the exact opposite. You have full caching, your infrastructure goes through the roof.
Serverless is predictable. You have a server function. When you deploy it, it goes live and it won't go live until you change it. If you introduce caching, there's a possibility for it to go weird. We've seen this happen where you've got timeouts and things like that. That's all fine. If you know what you're doing and you have a very specific use case, do it. You can do that with serverless functions now. You can return cache headers and TTLs.
But, when you try to automate that, it starts to get really tricky. There's a lot of pitfalls and so when we looked at DPR, what the goal was, of DPR, is to solve that problem by making it so that the way your build command runs -- and if your build command succeeds, it lives there. The eventual goal is you use DPR. The output of that function, called with the given path, will be stored in the CDN and returned as though it was a static asset generated at build time.
And what I really think is exciting about that is that this gives us a possibility, like, let's look at the model here...with a build, right, you run a command and then you get a folder full of assets. With DPR, you run a command and you get a folder full of assets. You can put those on the CDN and all of these live inside of one, atomic deploy. They will produce the same output predictably and that, I think, is very -- you know, that's a powerful way to look at this and a powerful way to manage it.
So, that being said, what is up, Luke! The Lolli site that Phil did, yeah. It was trying to rebuild the site with new data and, you know, kind of do a client-side rendered version. This is programmatic in a way that would be -- Jacob, that's absolutely right. My Slack notifications did not stay off. If you look at what we're trying to accomplish, we're trying to get it to the point where you, as the developer, don't have to think about caching. If you use the Jamstack model, the Jamstack architecture, unless you have an edge case, the caching settings are good enough that you're going to have a super-performant site, assets are going to get delivered quickly, it's very predictable. We want that same thing to be true for serverless functions when you have dynamic content. If I have 1,000 pages on my site, I could use my Google Analytics to determine what 20% of the pages are that get 80% of the traffic.
Then, I can have a post-deploy hook that would call the other 80% of the pages as, like, serverless functions. I can set up a loop and send a request to each one of those pages as an on-demand builder. My build command just drops down to 20% of the time, but I still get a fully-statically-rendered site. We get that immutable, atomic output that is predictable. We're not dealing with caching. Or, what I want to build today is something where it's more user-generated. What I want to build, today, is an app that will let you preview a color, based on URLs. And so, if we're going to do that, I realize I've been talking for 24 minutes, let's go ahead and do that so we can kind of see how that works.
Let's -- yeah, let's do this. So, I'm going to make a directory and we're going to call this "every color." And in "every color," I want to be able to -- we're going to run it as a Git directory. And then I'm going to the" NPM init." And then, I want to -- let's use Node 14 -- actually, can we -- Node 16 is out now, right? So, let's install 16. And then...yeah. Then, let's do "node version" and we'll put that into NVMRC so that Netlify uses Node 16. Then, what else do we want to do? What else do we want to do, chat? Let's install the helper functions we need. I'm going to install Netlify functions. I'm working with on-demand builders and these are going to let us do a whole bunch of stuff. It's very exciting. Do functions cap at Node 14? That's going to be hilarious. That's a good question, actually. Let me check that before -- before we do it.
You know what? Maybe I shouldn't. Maybe I should just use 14, because I know that 14 works and then we won't have to do that thing. Let's see, one [Indiscernible] override, I think. Yes. Okay. So, we'll use Node 14. Good call to save me from myself. Yeah, this is -- yeah. This command, here, is super-handy when you're trying to get Netlify to use a node version. I love that command. It's super, super helpy -- super, super helpful. It's helpy. AWS only supports 14. Good call, y'all.
Okay. So, we've got this thing set up. And what I want to do next is I'm going to open this thing up and let's start with -- I don't know. Let's start with just an index.html, maybe. And, what I'm going to do with this index.html, I'm going to use VS Code to get a real, simple document and we're going to say "every color." And in this, I'm going to -- let's do, like, a main -- I probably want, like, a header of some sort. What do we want to do here? I can just put, like, "nav." And we'll do a link to "home." And we'll call it "home." I think that's what screen readers want to see. If I'm wrong, please correct me. And we'll say, "every color."
Then, let's add -- nope, not in here. Get out here. We'll do a main tag and inside of our main tag, we can put, like..."every color." And we'll get instructions, in here, on how to do it but I have to think about how we want to do it a little bit. So, let's -- let's see. Let's see what's going to happen here. I'm going to...I'll just do "to do instructions." All right. So, let's get that running and the way that I'm going to get that running is I'm going to run Netlify Dev. Okay. Apparently Netlify CLI got uninstalled when I moved between my Node version, so let's just drop that back in.
I'm using the Netlify CLI for this. "Helpy," new word. So, let's get this thing installed here. Yeah, Tony, I could mix my environments. I'm not going to do that because I don't really need any of the features in Node 16. I was just reaching for the new shiny and I don't really need that.
And, I'm going to move this over to the right window. There we go. And, okay. So, now we have this kind of basic setup. Let's make it look nice. So, I'm going to create a new file. We'll put this under, like, "assets and styles.css." I'm going to do plain, old CSS. Let's do something like this. Let's maybe start with, like, an HTML. We'll do a font size of -- let's do 18. It's a little more readable. My eyes appreciate larger font sizes and that'll also help with inputs being the right size so we don't get zoom on [Indiscernible] and stuff like that.
Then, we'll do -- I don't know, like a line height of 1.45. That seems right. What else? What else do we want? Let's do a background color of -- oohh, this is what I want to do! Let's use -- let's use variables. So, I'm going to set one up of, like, "background." And we'll set it to start to be, like, white, I guess. That'll be fine. And then we can do variable of background, good. And we'll do a color. And probably make the text color, like...let's do, like, the darkest color of -- I don't know -- 3, 2, 1. Then we can do a text of -- we'll do, like, 5, 4, 3. Maybe. I don't know. That might look terrible. We can play with it. Let's do text and then we'll have our main and our main is going to have a background color, but we also want to style up like our H1, so we'll give that one a color of "darkest." Okay. So, let's save that and get a sense of what we've actually done here.
So, I'm going to link, rel, style sheets. Whoa! What was that? Style sheet. We'll do an HREF of -- let's make it absolute. We'll go "assets style css," because I think when we take this to the serverless function, we'll redirect everything and I want to make sure we include that properly. Okay. So, we've reloaded that. I'm happy with that. And let's do a little bit of styling in the browser. So, I'm going to pull this over here so that we can do some sort of mobile-ish styles.
I want my "main" to be -- let's go with a, like, max width of 90VW. We'll give it a margin of 5 REM auto. I'll do a border of one pixel solid. Darkest. And border radius of -- I don't know -- like 0.5 REM. Let's give it some padding of 2 REM, maybe not so much on the top. Yep. Okay. Also need to add a little bit more in here. Let's do a style like this. We'll do a margin "zero." Yes, I like that. And then let's add another one and we'll do -- one of these little jobbies. We'll set a margin top of 1 REM. Good? Okay.
Then I'm going to specify that to be under the body so that we don't get it everywhere. Now that we've got this, I've got that little bit to clean up in here. Has everybody seen this before? This is -- so, basically what this is doing -- if you've never seen it -- it's saying "everything should have a margin of zero." The outcome is that, like, this being the first element has no top margin if we inspect it. So, there's no top margin. But the "p" below it does. You can see it's got that one REM of top margin. And the main has a top margin, but the header does not. The reason I scoped it to "body," is because it would come after "head."
Yes, the lobotomized owl selector. He's like, "woo-hoo."
(Laughter). Hayden is the one who popularized this. There's an article you can look at if you're interested, which I guess I can just look up. Right? There it is. And, we'll include that in the show notes look at that little lobotomized owl. We're styles to the "main," which I'm going to grab out here. And let's drop these in here. And I'm going to resist my urge to alphabetize those, mainly because we've got things to do.
So, good. So, if I refresh this page, we're happy. You know what else I didn't do? I didn't set box sizing to "border box." And the reason I do this, the padding blew this out to full width. After setting "border box," the padding gets factored in and it doesn't do that anymore. Things are predictable. The width includes the padding so you're a little easier to deal with.
(Laughter). You thought I was just making that up. I'm not just making that up. I'm far from -- I'm far from clever enough --
Ahhh, behold! My bucket!
(Laughter). JASON LENGSTORF: Yes, indeed. The bucket. So, there's a reason that I set this up the way I set this up. And the reason is, I want to be able to modify the background, here. So, my idea is if we set it like this, we can do, like, background -- oh, this will be -- I need to move this. We can set the background to, like, "red." And then after I've done that, I'm going to move it up to the right level. Which will be the body, because the body is what has the -- are you going to let me add this? Hello? No, I can't do it. All right. So, let's add it in here. Okay. What should happen if I did this right -- there it is. So then we've got our background comes in and works, so let's clean this up a bit. I want my body to be -- min height is going to be 100, so that will fill the full width of everything. You know what else we can do? We can vertically center our "main." So, let's -- do I want to deal with that, because we've got the header in there? Nah, I don't want to do it.
I've got my body -- ohhh, that's going to get weird if I do it that way. I need to instead do "body" up here because that would have targeted everything. My body is going to be a min height. Let's go with "lightest." And we'll make "lightest" up here white. I'm going to use the background. No! Because I'm going to override the background.
Look at that. Look at it happen, y'all! We have -- we have all of that. You can set the root height to 100%? That's cool. Wait, does that actually work? Okay. All right. Bear with me while I do CSS experiments, because I'm really curious about this. Let's take this out and let's set the root height to 100%.
No, this will work. This will work.
JASON LENGSTORF: Yes! I don't see that. Does it need to be, like, 100? I don't think that one -- I might be doing it wrong. But that would be news to me, if that worked. So, let's, instead, do one of these. There we go. Okay. So, we've got ourselves a thingy. And now what I want to be able to do is I want to be able to set a style, so, like Hex, and then set a color. So, like, badass as a color. I want to show this color as the background so we can preview. Right. Like, I think that'll be really, really fun.
So, let's figure out how we can make that work. I'm going to add a function and what we can do now -- so we don't even have to configure it -- I'm going to do Netlify functions and inside this, I'm going to create a new file. And let's call this "color.js." And in here, we're going to use that Netlify functions thing. We want this to be an on-demand builder so we're going to import builder from -- we're in Node Land here. Let's require Netlify functions and we'll set up a handler and that'll be our function. So, I'm going to make this async. And then down here, we're going to do "exports handler equals builder handler." Right. So, this is a slight change from what you would normally do. The handler is what you have to export for the function to work. And we are just wrapping it. So, I could wrap it, like, I could put it up here, if I wanted, and that would be totally fine. It's a little cleaner to me, to look at it this way. I understand it better.
We need to return a status code and we'll return 200 and that means it succeeded. I'm going to do a status code of "okay." Or a body of "okay." So, if I stop this and restart it -- because we added functions -- we should see something change. Our function server is running. We're at Local Host 888, but nothing has changed there. If I go to Netlify Functions Color, it says "okay." So, we've got a function running and it'll live-reload for us, so this should be a color. I can save that and refresh. Okay. So, it's all working the way that we want. We can see that the requests are coming in. If I wanted to "console log," I could "consol log" here. Our development experiences is going to be really nice.
And it's worth pointing out that DPR is not happening here. We explicitly don't support that because it would be super-confusing if you were trying to develop on your machine and it started caching your results. When you are working locally, you'll see the result of a serverless function, not anything cached. We can start working on our redirects and so I'm going to -- how do we want to do this? Let's do it -- let's do it the simple way, yeah. Let's do a Reader X file. I'm going to say "everything goes to Netlify functions color." And it gets at 200. If I forced it, my home page would go through color as well, and I don't want that. If the page exists, it'll go through. It reloaded redirect roles. I might not have to stop and restart. Yeah. Okay. So, here we go. Now, I have been able to request my Hex. And it shows up in here and I can do something like -- let's go into our color and get some event data.
So, I'm going to get the path. I can just destructure this. Let's destructure from "event." And I'm going to log path. So, then when I come out here, okay. We've got -- we've got our setup. And so then, what I can do is I can get more details. So, what I want is I'm going to get the array details. So, I'm going to skip the first thing, because it's going to be empty. And then I want to get the type and then I want to get the values. And I'm doing it like this for a reason, that I will show you just a second. Let's path-split based on the forward slashes and then let's put in our type and our values and let's see what happens here. So, good. We've got our type and our values include badass. Good. So, we can see we want a Hex code and we want "badass" to exist. We could do 255, 255, 255. Then when I look, I can see my values as an array. This will allow me to do this work.
Let's see, so...what does the wrapper actually do? So, under the hood, what the wrapper is doing is it's adding meta details that let Netlify know that it is a builder function. So, functionally-speaking, it doesn't do a lot other than notifying Netlify that this should be handled as a function. It's including additional internal details that let Netlify know this particular thing should be cached.
There will probably end up being some config, in the future, that will allow you to do more. I don't know if we have any of that set now. Yeah. I think at the moment, there's no real, like, config. But, we have ideas. If you have ideas -- if there are things you want to see, please weigh in with, "I want to be able to configure this thing." They are headers, Chris, but they're custom, Netlify headers that we don't share because they might change -- they're basically, you know -- yeah. They are headers. They're headers, but because it's affecting, like, proprietary edge network stuff, you're going to return this thing for things that are flagged as builder functions, they're going to be, like, routed to the caching mechanism.
(Laughter). Yes, the, like, how we're going to make this work in Rust and Go and things like that, there are active discussions about that. So, I have no -- no, you know, no promises. But, definitely something that we're interested in and definitely someone, like me, is waving my arms about a lot.
(Laughter). But, yeah. So, this is -- this is pretty -- already, we're in pretty decent shape here. But now what I want to do is I want to figure out how we can get the -- the template, right, of what we're doing here, I want that to be returned. And so I'm going to do it in the most naive way that I can, because I don't want to get into anything particularly gnarly here. So I'm going to create a function called "get HTML." Naming conventions be damned. In the meantime, I'm going to return a tag template "literal," that will give us our HTML. This is all what I want. This is the part that we're going to have to calculate here, is we want to change this background to be whatever the color is that came in.
But what I can do is I can set some headers and I want to return a content type of text HTML. And the body will be "get HTML." And I can send type-in values. Then, out here, we could do something like -- I guess we could just dump it for now. We could say "the color type is "type" and its values are -- let's do "values join." I think that'll be better. So now that we've got this, we should be able to -- type is not defined. Oh, I forgot to add the arguments. Whoops! Forgot how code works. Hey! Value is RGB. Values are 255, 255, 255. Great! We are pretty dang close, here, to having this thing work and we've still got some time.
Let's go ahead and build out our ability to determine this color. So, let's see, what's the right way to do this? We could do a switch. We could do an "if" statement. We could handle it a lot of ways. I'm probably just going to do an "if" statement, but let's figure out -- so, we want to figure out if type equals Hex. We've got one -- one way of handling it. LSIF Type equals RGB. What are some other ways we want to do this? We can probably do "type equals HSL." Let me think. What else? What else, chat? So, if we do it that way, then we can do, like -- I guess we can set...oh, yeah, old school named color. LSIF type equals...let's call it "named." I think that's everything, right?
Let's write this and see if we can refactor it. There's probably a better way to do it. Let's start with a "let" and we'll call it "color value." If it's Hex, I want my color value to be...we'll do this. And then, we'll get the first values. I should probably make that a little bit easier. Let's do -- let's add a default. So, we'll get Hex value and we'll default that to black, out of values. So then, we can drop that Hex value in. And, what should happen, in that case, is if you put in some nonsense -- or you, like, just hit "/heck." If you put in nonsense, it would break because that would be value. If you leave it blank, then it'll default to "black."
If you go to RGB, then we would want to do a const of red. We can do RGB equals values and those, we can probably default to, like, 255. Because that's -- if you do all 255s, that is white for RGB. And then our color value will be RGB and we'll do the new syntax, because we're fancy! "RGB." And we could do an alpha, but I won't. So, we'll do that. If it's HSL, we'll get the same thing. So, it'll be "const HSL -- oh, boy. What are the defaults for HSL? Oh, I've made a mistake. Let's look here. CSS HSL...it is value, percentage and a percentage. And -- let's see. Let's do it this way. And the value is 0 to 360, I believe. 100%. 50%. So, we're going to skip the alpha part. And instead -- yeah. So, this will be zero. Saturation would be, um, I don't know, 100. And lightness would be 50? How about that? And that'll come out of values. And then our color value will be HSL. It'll be H, S, percent and L percent. And that should give us a good color value. And if our color value is -- is named, then we'll do that and we'll just set it to black. Equals values. And, we can set color value to "color."
Okay. So then what I should be able to do, down here, is just set that. Okay. (Barking). Unclosed pretag. (Music playing).
Let's just drop that out and, instead, what we can do -- let's test that it works and then we'll add some visualization. Here we go. What we should see, here, is a white background. Okay. If we go to "hex badass green," let's do hot pink. Let's do HSL -- I don't know what HSL colors are. 120, 150. That's a very green and then I think if we do -- what was it? Like, 240 should be green-blue? Yeah. Okay. So, all right. So, that appears to be working. Our RGB, let's do red. Okay. Cool. So, it's working, right. It's doing what we want. So, let's make this a little bit -- a little bit more visually-interesting. We've got our color value. How can we display this? And, we can say..."type RGB HSL named." Should we give it, like, a display? And that can just be, like, "hex." Oh, no, we need it up here, don't we? Display name -- I'm starting to see the value in -- in a light refactor here, but we can do RGB and we'll drop another one and we'll say "HSL." And then with this one, we can say "named." And then we can say...we'll go, like, "display name color." And then we can do "color value." Right? And we can drop this in both places.
Hey! Look at it go! So, now, we have -- ohhh, that max width is too high. The max width is too damn high. Let's make this width of, like, 600? 550? That's okay. That's pretty good. Could it be even -- what if it's, like, a really long one? What's the longest one we're going to get? Probably that, right? So, that wraps so I'm going to make it wider. Let's do 570. That seems okay. I'm happy with that. Let's do it!
This seems -- yeah, I also -- I also really like character for max width. But in this particular case, I just want the title to not wrap on me. And I think that's about the longest thing we would get, is all -- is RGB White, I think. But then, we can say -- let's add some instructions. Or, actually, why don't we do this? Let's show when it was built. Let's get the -- let's see, how's time going to work in Node, if I want -- because I can't just use, like, the date module, can I? That would be neat if I could. Let's -- let's see, "Node get local time." Do I need Date.now? You can use it! Hooray! Is it a global built-in, or do I need to -- I don't want to package, I just want you to tell me. Hello, Node! Whatever. I'm just going to do it. Let's do "const render time equals date.now," and let's drop this in here.
I bet I can do this as a locale string, too. That would be extra-handy.
(Laughter). No. So, we'll do it like this...there we go. Okay. That's better. So, now we're seeing when it was actually rendered and it's using -- oh, God! Can I make it an iso string? Okay. My brain can process that. Okay. So, this is fine. This is -- this is good. We're happy about this. We're getting information. It's showing us kind of when things were rendered. We can see, for local dev, it updates every time we call it. So, this will give us a good test of whether or not it's doing what we expect. And then, you know, if I go to "name top pink," so that does what we want. Let's also just check, really quickly, that it does what we want if we, like, don't give it a thing. RGB goes to white. HSL goes to red. Cool. Okay. Cool. I'm happy.
And then, in this header, I just realized, is not what we want to change. What we actually want to change is the title. So, that seems right and now we get -- oh, wait...yeah. Yeah! Look at that! So, now we actually get a Hex color and let's add in the app name. Okay. Okay. This -- this seems -- this seems great. So, I'm -- I'm happy with that. Like -- and this is going to be relative to GMT, because we're showing the GMT time zone and I'm not going to localize it because I don't care.
So, yeah, the -- the way that this will work, Jacob, is that the fallbacks would cache per path, like, it's the path that caches. So, any unique path, like, if you -- if you came in here -- actually, what happens if we just do "nonsense." Undefined color. So, I could probably do a 404, but I'm not going to stress about it. Anything you put in here would cache. Whatever thing you want to do, it's going to cache based on that.
Query string does not cache, so you can't cache. No, it's not where the servers are running. It's that the iso string defaults to GMT. It's giving us the time at the zero time zone is what's happening.
So, yeah. That -- that -- yeah, but we -- let's see, what happens if you have an additional path segment, will it trigger a new build? Yes. If we added, like, a -- whoops. That would be a new build. And if you keep going...all of these will work, but each of these would be a new build. So, you know, it's -- you would potentially want to have, like, patterns for this and ways of catching it in your URL, for proof-of-concept, like this, where we're kind of taking user input, I'm not super-worried about it. And, you know, we're not evaluating code or anything. It should be fine.
If there is a way you can do XSS through a CSS variable, that would be fantastic. Hello! Bye!
Redirects, redirects work like this...and if you wanted to do, like -- let's see, Hex value and anything and then you could go to "Hex value 200," then that -- actually, that might do what we want. No, that didn't work. So, I, instead...hmmm, why didn't that work? Oh! I know. Because it needs to be a 301 because we're not masking it anymore. Why did it redirect too many times? 200 to -- is it this? No. Okay. So, maybe that doesn't do what I want. Maybe it's, like, more stuff. I have to do it that way. Okay. So, that works. Aha! That is what it was. So, this will include "empty," which is why it was including a redirect loop. And that actually doesn't matter. By adding one more segment, at least one more segment, we can say..."always redirect to the right thing." And then I do "no more why please stop" and it's always going to redirect to the right place. So this would always work so we'd always be caching at the right thing.
Nikki is asking, would it return results if it's adding to the URL. So, do you mean, like, if somebody were to say, like, UTM source or whatever? I don't remember exactly how those work. But, you know, it's like Dev 2. Yes, this would still be cached. We'd disregard query strings. Only the path name. The path name is the only thing that is for the cache. Is DPR available for everyone? Yes. Yes, it is. It's on this page, here. You can install this package. The builder Netlify functions and import this. We're calling it a beta because we're not quite sure how people are going to use it. We think this is a good idea. We have a lot of faith, in this approach, to unlock a lot of pretty complex use cases, but the reason we are leaning on the beta -- and this RFC -- is we're really interested in hearing how people are going to use this. What are they going to try to do with it? What happens if you try it for a production use case? But, yeah.
If you want to overwrite a DPR file, if it was a blog post and the content changed, can you update it? Right now, you would trigger a new build and then that would clear that cache. That is to support the idea of atomic builds of -- you know -- basically making it so that content won't change unless there are builds so that you can roll between them.
And so, that -- that is -- is a conscious choice of ours. There is a discussion about what would we're tentatively calling refresh hooks, which is the idea that you could say "I didn't change the code on my site, but content that's been dynamically-rendered changed. Copy the files over as a new, atomic deploy." That is in discussion so that you maintain that ability to rollback. So, my guess is that we will have a way to do it. But today, we don't, because it opens up some doors about, like, making it difficult to track a state of content. It removes some predictability about how content works with deploys.
So, this is pretty exciting and now I would like to take this thing live and see how it works. So, maybe we should add a little bit of details here, about how this thing works. So, let's go to our index page and say -- we'll say, "preview any color using X RGB, HSL or named CSS colors right in your browser." And then we can do one of these for each one. So, Hex, use...let's see. Like, Hex and...that's not going to do what I want is, is it? It needs to be "greater than" and "less than." This is not actually what I wanted. I wanted code. Writing Markdown over here. Code. Okay. And then for RGB, it'll be RGB and...0 to 255. And then we'll just copy/paste that, three times. Okay.
Then, for HSL, we can do HSL and 0 to 360 -- I guess -- well, really, zero to whatever you want. It'll just loop around. And then 0 to 100. And 0 to 100. And then, named colors...oh, I typoed that. HSL. Named, um...should I add an example? Probably. Let's add a link. So, we can do, like, Hex... right? This seems okay. This seems like it's working. This seems like what we're doing. Let me close that down. Why you double-tagging on me? Let's maybe shut the drawer. Shut the front drawer. Okay. So, let's get that out of the way. And, drop in, like, an RGB and we'll do a -- I don't know -- a 255, 120 and 200. I don't even know what color that is, but it's going to be fun. Okay.
And then, let's do one more of those. Two more of those. We're going to do an HSL. So, let's do a "copy." HSL, we'll make it -- let's make it nice, vibrant green. And, last, but not least, we're going to do a named color. And, on this show, we [Indiscernible] so we're going to do tomato. Okay. All right. That seems like it'll be okay. Let's go back to the home page and look. Is that a break after these? That's a lot after these, otherwise.
Ohh, that's a nice color! Oh, I nailed that! Go, me! And then we can go through to -- yeah, tomato. Good. All right. So, I have not styled this thing, but I'm going to be okay with that. Let's -- hmmm, I don't like the way that that happened. Let's do, like, a padding bottom -- actually, let's do this. Let's do a padding of two REM, at the top and the bottom. We'll do zero on [Indiscernible]. There we go. That's better. Now it looks okay. And then, our header, I should style, but I won't. I'm just going to leave it.
But that's going to get really inaccessible, isn't it, if I make it, like, "named black." Yeah, you wouldn't be able to see that at all. Let's get a header in there. We're going to make this all the same things about -- actually, why don't we just do it like this. Right. And then we can pull some of the -- the specifics out of "main." So, I do like the padding. I do like the border radius. Do I just want it all? Yeah. So, then let's overwrite the margin top to be 2 REM. Margin top, 2 REM. What am I -- oh! That's a little better. Look at that! Beauty! Beauty! We're winning design awards today, y'all. Okay. So, then we can go back here. We can click on any color. Get in there. Great. All right. So, then let's make that header "A." And we're going to do a font weight fold and color of "inherit." And what else? Let's do...text decoration, none? I want the color to be "darkest." Yeah! Right? That seems okay. I'm okay with that. Yeah, yeah, yeah! Look at this beauty! I'm so happy about this!
And then, I guess we can -- yeah, I'll clean that up a little bit more, with a footer and details. For now, I want to deploy this thing. So, I've got my basic setup here. I'm going to -- let's see, we need to ignore our Node modules and that's about it. So, let's echo Node modules into a "get ignore." Our Node modules are gone so I'm happy to save all of this stuff. So, let's "add all." We're happy. Commit. And we're going to say "feature, use DPR to show every color." All right. So, then I'm going to use the GitHub CLI. I want it to be "Learn With Jason," every color. It's going to be public, yes. Done. Okay.
So, then, going to get, push, set the upstream to "origin main." That's done. Let's Netlify init this thing. We're going to put it on My Team. We're going to call this Every Color -- that might be taken. "Every Color LWJ. "We don't need a build command. We're using Netlify functions. Okay. So, then if I open, this will take us over to the Netlify app, where we can see this thing deploying. There we go. No build command. So fast when you don't have any build command. 16 seconds.
All right. So then let's go take a look at this thing, live. And let's see what we see. So, we go to badass and it's at 184826. It is not changing when I refresh because I cached. The reason this is beta is because this isn't complete. It's cached at the CDN nodes. You'll hit a different CDN Node. Depending on which one you hit, it's going to be local to you. And, the way that CDN Nodes work is that they are least-recently used caches. If your site doesn't get a lot of traffic, this will actually fall off and need to get re-rendered, which -- like, that will be fixed. So, right now, this doesn't do exactly what we want, the end of the world -- like, the end of the -- the end state of DPR and on-demand builders is going to be more permanent. It'll go to the cache, itself. But for now, it's living on the edge nodes, which is, you know, better than it was, not as good as we want it to be at the end.
So, this is really exciting. It's really interesting technology. As we click around, we see this got rendered at 1849. Did it just re-render already? So, like I said, the caches are -- depending on highly-trafficked they are, they will hit more -- or less -- often.
So, right now, Frank, you cannot specify the cache control in an on-demand builder. That is an intentional choice because managing caches manually tends to be a lot of a foot gun. If you know what you are doing, you can send back cache control headers. Like, you can always send back a cache control header here, and manage that on your own.
We -- there's nothing stopping you from doing that. We don't do that, specifically because we don't want it to be confusing. We -- for us, it's -- if it's been deployed, it will always return the same thing and that is very much by design. If content or code changes, you create a new deploy and that will get you the latest version of the thing. The discussion around refresh hooks, you can add your voice to the RFC and we can talk about how that would work.
Does it give you a cache hit/miss header? I don't think it does, but let's look. So, here's what we're hitting. Response headers, we get back Netlify. We don't get a -- we don't get a hit. It doesn't -- it doesn't show that. Um...that is a good idea, though. Like, that would be a good thing to include.
What we can see, though, is that the request time's go significantly down. You know, it's 40 milliseconds to get the page. If we go to a new one, like, we go to, let's say, 120, 50, it's 138 and then 41. 41. 41. So, you know, serverless functions are fast. But this gives you an ability to really cut those times down, even more.
Nikki is asking, is this the feature that Zack used to get rid of images on the docs. Yes. He's rendering those, on-demand, using the on-demand builder.
What Landon is saying is this is cool because any asset that can be fetched with a URL. Absolutely. You can use it to render images. You can use it to -- like, I use it -- actually, here's a real-world use case. I use it on the Learn With Jason site for the API. So, if you go to Episodes, on the Learn With Jason site, it's going to bring you back all the episodes. But it does that, as an on-demand builder so API requests stay nice and snappy and you are able to get all the episodes quickly.
That, to me, is -- is, like, so useful. I -- I'm very much a fan of that. So, it took one second the first time, because I've got to hit my Sanity API. It's very, very fast to get these, once they've been cached. And as this technology matures, and that changes from being just the CDN edge node, it's going to be a really powerful way to roll this stuff out.
So, that being said, any follow-up questions, before we start shutting this thing down? Do you redeploy the entire site when you update the list in Sanity? I do. I rebuild pages for the site, when I update my Sanity list. I could potentially do -- do different things to make that less complex. If I had refresh hooks, I could potentially eliminate my builds and just kind of, you know, copy the files forward to get atomic builds with the on-demand rendering of pages. That would be huge. We're talking about how to do it -- what we're trying to do is we're trying to avoid that factorial explosion of possibilities.
We're talking about on-demand builders, that's what we've been working with today.
Does a redeploy wipe the whole cache? Right now, it wipes the whole cache and that's, again, very intentional. We want each deploy to be unique. Now, what Netlify will do, under the hood, is it will check to see if assets can be pulled forward and not have to be removed from somebody's browser. As far as, there is a folder full of assets that is your deploy. And that -- each time we're going to make sure you've got a unique copy of the folder at that state and time. If the file hasn't changed, we'll still duplicate it but we will do smart stuff, in the edge network, to make sure you're not doing a full re-download of a change.
Again, that is something that if you try to do that on your own, it can get hard. Like, it can get really challenging, so, I definitely -- like, what we're after here -- the whole motivation is to do -- do solid defaults. We want to make the right thing, the easy thing. You, as the developer, shouldn't have to think about ops, deployment, caching strategies. You should be thinking about how to build a frontend. And when you commit it to GitHub, it should just go live. When you get this workflow going, it enables that. You can light, middle-tier backend work. You and your team is more productive. Your company's happier because you don't have these dependencies, and things like that.
Let's see...oh, that's a good idea, to do an iCal version. I would be super-open to a PR. That is a very -- very, kind of fast, high-level overview of DPR. I hope it's exciting for you and something you're looking forward to.
We've been having the whole show live captioned by white folk. Which is wonderful. It's right on that Learn With Jason home page. We have Netlify, Fauna, Auth0. It means a lot to me.
While you're checking out the home page, make sure you look at the schedule. There's a chance I'm going to be recording a podcast over my Thursday slot today, so, if not, I'll be back on Thursday. Otherwise, I will see you next Tuesday, where I'm going to Gregor Martynus talking about Octokit. If you haven't used Octokit, it is dope! A lot of CLI stuff. We're going to build some automations. We've got really interesting ideas.
(Barking). Then we're going to learn Kotlin. I don't know anything about Kotlin. We're going to use TypeScript. Better screen reader experiences. I'm also in conversations, right now, to do even more really good stuff, like, I'm going to drop a couple hints. We've got Marcy coming back to the show. Please stay-tuned. Head over to the schedule and add that to your Google Calendar.
That being said, we're going to go find somebody to raid. Chat, thank you, as always, for hanging out. We will see you next time.