1. Introduction
Today we're going to talk about FRESH, a full-stack web framework for Deno. I work at Netlify. I'm from Montreal, Quebec, Canada. If you're looking for me online, I'm at NikkieTOnline pretty much everywhere. If you want to know more about me, visit IAmDeveloper.com. I also stream on Twitch and have a YouTube channel.
Hi, everyone. Today we're going to talk about FRESH, a full-stack web framework for Deno. Before we get started, a little bit about me. As mentioned, I work at Netlify. I'm from Montreal, Quebec, Canada. If you're looking for me online, I'm at NikkieTOnline pretty much everywhere. If you want to know a little more about me, you can visit my website at IAmDeveloper.com. I also stream on Twitch, so if that's something you're interested in, you can check out IAmDeveloper.live. I also have a YouTube channel that you can check at YouTube.IAmDeveloper.com. I am also not a big fan of spiders.
2. Introduction to Fresh and Deno
Today we're going to cover what Fresh is, discuss web standards, and dig into the features of Fresh. Deno is a runtime for JavaScript, TypeScript, and WebAssembly (Wasm) that uses V8. It has built-in linter, code formatter, test runner, and interoperability with Node.js and npm. Deno follows web standards and is part of the WinterCG, a community group for API interoperability. To install Fresh, use the command 'deno run-A-R https://fresh.deno.dev project-name' and choose options like Tailwind and VS Code integration. Start Fresh with 'deno task start' in the project's root folder.
Alright, so what are we going to cover today? We're going to go over what Fresh is. We're going to discuss web standards, and then we'll dig into the features of Fresh. After that, there'll be a short demo, and then we can move on to questions and comments.
Alright, let's get to it. So, what is Fresh? Well, hold on. First, we need to talk about Deno. So what is Deno? Deno is a runtime for JavaScript, TypeScript and WebAssembly, or Wasm, that uses V8. For the web, it runs on the Edge. You can also use Deno to create command line interfaces, i.e. CLI's. It's got a built-in linter, there's a built-in code formatter, a built-in test runner, there's node.js interoperability via node specifiers, and there's also npm interoperability via npm specifiers and CDNs.
Alright, let's talk web standards. So, Deno uses web standards. For example, import maps, fetch, request, and response. Like the little drawing over there says, just look it up on MDN. Great docs, but also that's pretty much what you'll need to reference most of the time if you're working with fresh. As part of web standards, Deno is a part of the WinterCG. Web Interoperable Runtimes Community Group. It's a space to collaborate on API interoperability for JavaScript runtimes. Feel free to read more about the WinterCG at wintercg.org.
All right, so where were we? Assuming that you have Deno installed, getting fresh installed is pretty quick. You can just run the command that you see on the slide deck here. So that's deno run-A-R and then https://fresh.deno.dev and the name of your project. That was a lot to say. All right. The installation is pretty quick and you have a couple of options. You can choose Tailwind for Styles, go with VS Code integration via the Deno VS Code extension, and that's pretty much it. We'll go more into the styling story a little later in the talk. To start Fresh, we go into the root folder of the project in a shell and run Deno task start. We won't get into it in this talk, but Deno has a built-in task runner that you can configure via a deno.json file.
3. Introduction to Fresh
Fresh is a full-stack web framework that runs on Deno. It provides TypeScript support out of the box and uses Islands architecture for client interactivity. Preact is used on the server and client side, offering JSX support. We'll explain Islands architecture in more detail later.
All right. So let's dig into what Fresh is. So what is Fresh? It's a full-stack web framework that runs on Deno. It's server-side rendered framework. It's got just-in-time rendering on the edge. It provides TypeScript support out of the box, and there's nothing to configure to get up and running. There's no build step. There's no JavaScript delivered by default. It uses Islands architecture for client interactivity. It uses Preact on the server and client side. And there's JSX support thanks to Preact and TypeScript. If you're unfamiliar with the amazing Preact project, it's a fast and smaller alternative to React with the same modern API. It only gets a mention here for now, but we'll dig into what Islands architecture is a little later in the talk.
4. Features of Fresh
Fresh features include static files, routes and routing, and data-fetching. Static assets in the Static folder can be cached using the built-in asset helper. Fresh supports three types of routes: handler, component, and hybrid. File-based routing patterns are supported, but Fresh only supports server-side rendering. For data-fetching, handler routes require exporting a function that returns a response, while hybrid routes use the handler object to define functions for actions. Props in Fresh are accessed through the props.data property.
Alright, let's go over some of the features of Fresh. We've got static files we'll talk about, routes and routing, data fetching, middleware, error pages, styling, and then we'll get to Islands.
So first up we have static files. All static assets can be found in the Static folder. Static assets, aside from image and source tags, do not have cache headers set on them. You can set cache headers manually or you can use the built-in asset helper to automatically cache an asset for a year. Here's an example of the asset helper in action. So I have this on my style sheet. I'm using the asset helper and what happens is it generates a unique URL which is composed of the asset file path, followed by a query string with one key, underscore FRSH, underscore C with a value that is the build ID from the deployment. And you can see, as I mentioned, there also gets this in the cache control headers set in the response. So we have it there for a year.
Alright, let's dig into routes and routing. There are three kinds of routes. There's the handler route, which is typically for APIs, the component route, which is for pages, and then the hybrid route. And for hybrid routes, it's for pages that require handlers routes, for example, a login page or a search page. Like in other frameworks, routes can also be dynamic. You can see in the table here that I took from the fresh documentation, that there are many types of file-based routing patterns that are supported. One thing to note, though, is fresh only supports server-side rendering. So there is no concept of something like get static paths in Next.js or Astro since pages are never statically generated.
Alright, let's look at the data-fetching story. So for data-fetching, we have handler routes, which I just mentioned, and we can also use hybrid routes to handle data-fetching. For a handler route, all that is required is exporting a function that takes two arguments, a request and a context, and it returns a response. So in this case here, we have a handler which is using the, this is the jokes API that ships with the fresh demo site, and we can see here it's just generating a list of random jokes from an array, and then it's returning that as a response. For hybrid routes, we define functions for actions in an exported variable called handler. We name the async functions in the handler object after HTTP verbs. For example, get. As the handler route in the previous example returns a response, typically in a hybrid route, you'll want to return the result of the context render method. Think of context render as passing in server-side rendered props to the page being rendered. When I say props, I mean pre-act props or react props if you're new to pre-act. Looking at the example on the slide, one thing to note about props and fresh is that it's not props.movies, the props from context.render are always in the props.data property.
5. Middleware and Headers
The array of movie props is actually props.data, not props.data.movies. Fresh supports middleware, and multiple middlewares are supported. An example is given where two middlewares are used for a dynamic movie route. The least specific middleware runs first, adding headers to the response. The result shows the cache control header and the headers provided by the middlewares.
So for example, the array of movie props is actually props.data, it's not props.data.movies. That's a lot of dots in that talk.
Alright, we're going to move on to middleware. Again, like other frameworks, fresh supports middleware. All our files are named underscore middleware dot ts and need to reside in the routes folder. Multiple middlewares are supported. Although Deno encourages you to use TypeScript, you could write the middleware in a JavaScript file.
Let's look at an example. Say I navigate to slash movie slash Top Gun. Since we're hitting a dynamic movie route, that means we're using two middlewares. One in the root of the routes folder and one in the movie subfolder. In the case of multiple middlewares, the least specific one runs first. In our example, that means the root middleware runs first. It adds an X-conference header with the value nodeCongress2023. Then the second middleware runs in the movie subfolder. It adds an X-movie-page header along with a cache header that caches the page for 60 seconds. If we navigate to slash-movies, we can see the two middlewares giving a nice little handshake there. We can see the result in the response is that we get the cache control header and we get the two other headers that the two middlewares provide.
6. Error Pages and Unknown Page Props
We're going to talk about error pages and how you can define custom error pages in Fresh. The example given is a custom 404 page or file not found page. These pages are component routes with special props passed in. The unknown page props give you access to information like the URL of a page that was not found. For dynamic routes, you can use context.render not found to render the 404 page if the page does not exist. If you're on a page associated with a dynamic route and the page being loaded does not exist, context.render not found will pass the unknown page props to the 404 page.
So the X-conference and the X-movie-page. Okay moving on we're going to talk about error pages. Like other frameworks, you can define custom error pages. For example, the current slide shows a custom 404 page or file not found page. These are component routes but they have special props passed in. In the case of the 404 page, you have access to the unknown page props when the page is rendering. The unknown page props gives you access to things like the URL of a page that was not found. For a dynamic route, we call context.render not found to render the 404 page if the page does not exist. And just to bring it back to data fetching, when we were discussing data fetching, I mentioned that we need to typically return the result of context.render for hybrid routes. That still holds, but as I mentioned, if you're on a page that is associated with a dynamic route and the page being loaded does not exist, we return context.render not found instead, and context.render not found will then pass the unknown page props that we were talking about a second ago to the 404 page.
7. Styling Options and Future Development
Fresh gives you the option to enable TWIN, a server-side rendered implementation of Tailwind. Modern CSS is a compelling choice, with nested selectors, CSS variables, and other goodies. Adding a build step for CSS tooling goes against the no build step promise of Fresh. We may see more server-side rendered implementations of Sass, Post CSS, etc. Scope CSS, like in Vue and Svelte, would be a great addition.
All right. Let's move on to styling. When I first talked about fresh, I mentioned that there was no build step. So how does that affect styling our apps? Fresh gives you the option to enable TWIN, a server-side rendered implementation of Tailwind. This is great if you use Tailwind, but if the projects you're working on don't use Tailwind, what are your options? First, I would say modern CSS is pretty awesome these days, so you could literally go with a good old style sheet. The fact that nested selectors are coming to browsers and we only have CSS variables and other goodies like has in CSS makes this a compelling choice. You could also add a build step that commits the build artifacts of your CSS tooling. You could accomplish this with a GitHub action, or some other form of automation. Although it would work, it doesn't feel right committing build artifacts to the code base, and it also goes against one of the promises of fresh, no build step. I think what we're going to see here is innovation from user land where we may see more server-side rendered implementations of stuff like Sass, Post CSS, etc, much like the TWIN project. Another thing that I would love to see come to fresh is Scope CSS. We have that in other frameworks like Vue, Svelte. I think that would be a great addition.
8. Introduction to Islands Architecture
Islands enable pockets of interactivity in your site or application. Fresh uses islands architecture, coined by Jason Miller, the creator of Preact. Only the JavaScript for the islands is loaded when a page with islands loads. Fresh has two component folders: components and islands. Components in the components folder render server-side only, while components in the islands folder render server-side and revive client-side interactivity.
Alright, let's talk about islands. What is an island anyway? I mentioned it briefly when describing the architecture of fresh, but we'll dig into architecture a little more now. Long story short, it enables pockets of interactivity in your site or application. I'll leave this chunk of a blog post from Jason Miller up for a minute, but Jason Miller, one of the folks alongside Katie Seiler-Miller, coined the term islands architecture.
It's actually fitting that Jason is one of the folks that coined this term, as he's also the creator of Preact, which Fresh uses. In islands architecture, the goal is to ship mainly static HTML and then mark certain regions of the document object model, i.e., the DOM, as available to hydrate, or to use Fresh's terminology, revive. When a page with islands loads, only the Javascript for those islands is loaded. I mentioned that Fresh serves zero Javascript to the client by default. So, how do we enable the client-side interactivity that we're talking about in islands architecture?
Fresh has two component folders. There's a components folder and an islands folder. Components in the components folder will always render server-side only, even if you add client-side interactivity to them. And components in the islands folder will render server-side as well as once the page loads and the client-side interactivity will be revived. So, let's look at a classic interactive component – a counter. It has a couple of buttons, if you click plus one it increments the counter, and if you click minus one it decrements the counter. And in this case here, we have a couple.
9. Reviving Islands and Demo
Components that are islands get rendered server-side by Fresh. The rendered markup for the islands is visible in the view page source. Fresh adds a script with the ID __FRSH_state, containing an array of props for each island. The revive function in Fresh takes the list of components and the initial state to correctly map the initial state. Island components in Fresh are rendered with HTML comments, denoted by FRSH followed by the component name and the array index of the island component state. This is an implementation detail managed by Fresh. The demo site showcases counter components and a movie list with middlewares running.
So, how do we revive an island? So, essentially what happens is the components that are islands get rendered server-side by fresh and if you were to do a viewpage source of the page that loaded you will actually see the rendered markup for that particular island or islands. Along with the markup that's rendered for those islands, fresh also adds a script of type application JSON with the ID underscore underscore FRSH underscore state as the ID and in there there is an array. In that array the first element is another array which is an array of all the props for every island that's in the page. The second element in the array is currently empty there, but that's for plugins. That's something we're not really going to touch on today but if you're interested in the plugins you can take a peek at the fresh documentation.
So as you can see here I have JSX for my counter and it starts off on the server side where I say I'm passing in a prop with the value three for the start prop and when it gets rendered on the page we'll see that the initial props for that particular component get loaded in the array I mentioned. So how does it get revived? So we have that fresh state that I just mentioned and then one of the things that fresh does is alongside the JavaScript related to that particular island it has a revive function and that revive function takes the list of the components so for example here the counter and it also passes in the first element of state which is all the props for all the islands as I mentioned. In the previous example there was only one island but if there was more than one island how does Fresh map the initial state correctly? So if we have a couple here we will see that they just keep getting added to the array but how does Fresh know that the counter with the start prop of 3 and the counter of start with prop 5 maps correctly to the array there.
We talked about denoting interactive regions in the DOM when talking about islands architecture. Island components in Fresh get server-side rendered as I mentioned but they also get rendered with HTML comments surrounding them. The comment is prefixed with the FRSH followed by the name of the component, colon and then the array index of where the island component state lives in the initial state for all islands that was initially loaded. Doesn't matter how many different island components there are or how many instances of each, the array index will be incremented in each HTML comment based on the order of the islands in the DOM. One thing to note is that this is just an implementation detail, you'll never need to manage this yourself, I just find it useful to understand the underlying technology.
Alright, we're gonna move over to a short demo. So I'm just gonna go ahead and move us over here. So what we have here is... I just made a small demo site here. It's got a few pages so the home here where we have some islands so I have three counter components here. They're each managing their own state. And I also have a list of movies. That goes to the movie route. And we can load up a movie, for example, like Top Gun. And if I open up the network panel here, let's refresh that again, and this has two middlewares running. And like I said, there was the first middleware in the routes root folder and we can see that it has the x-conference header that that middleware provides. And then the subfolder middleware adds the x-movie dash page header, as well as the cache control of 60 seconds. All right, so we can close that. And here's an example of a hybrid page. So I'm just going to add a movie. So let's say, Lord of the Rings. And I'll give it a rating of five, and I'm going to submit it.
10. Demo Wrap-up and Resources
In this demo, we used a hybrid route with handlers to post new movies to the page. The demo used an in-memory database instead of a real database. The view page source shows how Fresh starts up, loads the counter JavaScript and styles, and caches assets for a year. The rendered code includes the components, Fresh state, and the revive method. The demo site is deployed at imdeveloper.com/fresh-demo, and the source code is available on GitHub. Additional resources on Fresh, Deno, Preact, TypeScript, web standards, and Dino's node compatibility can be found at imdeveloper.com/fresh.
So we're on the same page, and the hybrid route has handlers in it, in the handler object. And in this particular case, I'm passing a post, and that's what allows us to post back to the page and add the new movie here. For the purposes of this demo, I didn't use a database. It's just using a variable, so an in-memory database. Talking about data is totally out of scope of the talk, but just know that there's a lot of people working on the data problem on the edge.
All right, so we're just gonna kind of wrap up the demo here with... This is what the view page source looks like, of that first page with the three different colored citrus fruits. I've taken out a lot of things just so that I can scroll through it. The first thing is this is fresh just starting up here, that's what that script is. You can see the folder here is based on the build ID. And then we can see that it only loads my counter JavaScript, which is for the island. And then we have my style, like I mentioned, and it's using the asset helper, and it's giving it a unique ID, and it's got the... It's cached for a year, as I mentioned.
I just wanted to show briefly here, so this is literally the code that got rendered, minus I took out some SVGs for brevity, but we can see here, for example, that I have the first component here, it's denoted with the HTML comments, and then there's a second one, and a third one. Then we have the fresh state here, and we can see there, the three there. And then we have that revive method down here, and we can see that it's running here, and we have our counter component, and then the state we're passing in, which is the props for all that. And that's pretty much the demo.
Alright, let's move back to the slide deck. And yeah, if you're interested in the demo, it's deployed at imdeveloper.com/fresh-demo. You can also view the source code, it's at github.com/nickyt-online/fresh-talk-demo. And we'll just finish off with just some resources that I think you'll all find useful. So there's a bunch of stuff about fresh, Dino, preact, TypeScript, web standards, and also just some newer things with Dino like the node compatibility, and also a link to the WinterCG. For folks interested, the slide deck is available at imdeveloper.com/fresh. And that's pretty much it. We couldn't cover everything in about 20 minutes, but I hope this gave you all enough of a primer to get excited about fresh.
Comments