Fresh is a web framework based on Deno and Web standards built to run on the edge
Fresh: a new full stack web framework for Deno
AI Generated Video Summary
Today's Talk introduces Fresh, a full-stack web framework for Deno, and covers its features, such as static files, routes, and data-fetching. It also discusses middleware, error pages, and styling options. The Talk explains the Islands architecture used by Fresh to enable client interactivity. The demo showcases the use of hybrid routes and islands components, and resources for Fresh, Deno, Preact, TypeScript, and web standards are provided.
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
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.
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.
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.
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
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.
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.
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
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.
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.