Fresh 1.0
Fresh is a new full stack web framework for Deno. By default, web pages built with Fresh send zero JavaScript to the client. The framework has no build step which allows for an order of magnitude improvement in deployment times. Today we are releasing the first stable version of Fresh.
Client side rendering has become increasingly popular in recent years. React (and React-like) pages allow programs to build very complex UIs with relative ease. The popularity shows in the current web framework space: it is dominated by React based frameworks.
But client-side rendering is expensive; the framework often will ship hundreds of kilobytes of client-side JavaScript to users on each request. These JS bundles often do little more than rendering static content that could just as well have been served as plain HTML.
Some newer frameworks also have support for server side rendering. This helps reduce page load times by pre-rendering on the server. But most current implementations still ship the entire rendering infrastructure for the full application to every client so the page can be fully re-rendered on the client.
This is a bad development - client-side JavaScript is really expensive: it slows down the user experience, drastically increases power consumption on mobile devices, and is often not very robust.
Fresh uses a different model: one where you ship 0 KB of JS to clients by default. One where the majority of rendering is done on a server, and the client is only responsible for re-rendering small islands of interactivity. A model where the developer explicitly opts in to client side rendering for specific components. This model was described back in 2020 by Jason Miller in his Islands Architecture blog post.
At its core, Fresh is a routing framework and templating engine that renders pages as they are requested, on a server. In addition to this just-in-time (JIT) rendering on the server, Fresh also provides an interface for seamlessly rendering some components on the client for maximum interactivity. The framework uses Preact and JSX (or TSX) for rendering and templating on both the server and the client. Client rendering is completely opt in on a per component level, and as such many applications will ship no JavaScript to the client at all.
Fresh does not have a build step. The code you write is directly the code that is run on the server and client-side, and any necessary transpilation of TypeScript or JSX to plain JavaScript is done just-in-time, when it is needed. This allows for very fast iteration loops with instant deployments.
Quick Start
To really explain what makes Fresh special, let’s scaffold a new project and look at some code:
deno run -A -r https://fresh.deno.dev my-app
This script generates the minimal boilerplate required for a Fresh project in
the folder you specified as the last argument to the initialization script
(my-app
in this case). You can learn more about what all the files mean in the
Getting Started guide.
my-app/
βββ README.md
βββ deno.json
βββ dev.ts
βββ fresh.gen.ts
βββ import_map.json
βββ islands
β βββ Counter.tsx
βββ main.ts
βββ routes
β βββ [name].tsx
β βββ api
β β βββ joke.ts
β βββ index.tsx
βββ static
βββ favicon.ico
βββ logo.svg
For now, direct your attention to the routes/
folder. It contains the handlers
and templates for each route of the application. The name of each file defines
which paths the route matches. The api/joke.ts
file serves requests to
/api/joke
for example. The folder structure might remind you of Next.js or
PHP, as these systems also use file system routing.
Let’s take a look at the routes/index.tsx
file:
import Counter from "../islands/Counter.tsx";
export default function Home() {
return (
<div>
<img
src="/logo.svg"
height="100px"
alt="the fresh logo: a sliced lemon dripping with juice"
/>
<p>
Welcome to `fresh`. Try update this message in the ./routes/index.tsx
file, and refresh.
</p>
<Counter start={3} />
</div>
);
}
The default export of the route is the JSX template that is rendered server side for each request. The template component itself is never rendered on the client.
This poses the question: what if you do want to re-render some parts of the application on the client, for example in response to some user interaction? This is what Fresh’s Islands are for. They are individual components of an application that are re-hydrated on the client to allow interaction.
Below is an example of an island that provides a client side counter with
increment and decrement buttons. It uses the Preact useState
hook to keep
track of the counter value.
// islands/Counter.tsx
import { useState } from "preact/hooks";
interface CounterProps {
start: number;
}
export default function Counter(props: CounterProps) {
const [count, setCount] = useState(props.start);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
Islands have to be placed in the islands/
folder. Fresh takes care of
automatically re-hydrating the island on the client if it encounters its use in
a route’s template.
Fresh is not just a frontend framework, but rather a fully integrated system for writing websites. You can arbitrarily handle requests of any type, return custom responses, make database requests, and more. This route returns a plain text HTTP response instead of an HTML page for example:
// routes/api/joke.ts
const JOKES = [/** jokes here */];
export const handler = (_req: Request): Response => {
const randomIndex = Math.floor(Math.random() * JOKES.length);
const body = JOKES[randomIndex];
return new Response(body);
};
This can also be used to do asynchronous data fetching for a route. Here is a route that loads blog posts from a file on disk:
// routes/blog/[id].tsx
import { HandlerContext, PageProps } from "$fresh/server.ts";
export const handler = async (_req: Request, ctx: HandlerContext): Response => {
const body = await Deno.readTextFile(`./posts/${ctx.params.id}.md`);
return ctx.render({ body });
};
export default function BlogPostPage(props: PageProps) {
const { body } = props.data;
// ...
}
Because Fresh is so reliant on dynamic server side rendering, it is imperative that this is fast. This makes Fresh very well suited for running on the edge in runtimes like Deno Deploy, Netlify Edge Functions, or Supabase Edge Functions. This results in rendering happening physically close to a user, which minimizes network latency.
Deploying a Fresh app to Deno Deploy takes only a couple of seconds: push the code to a GitHub repository, then link this repository to a project in the Deno Deploy dashboard. Your project is then served from 35 regions across the globe, with 100k requests a day included in the free tier.
Production Ready
Fresh 1.0 is a stable release and can be relied upon for production use. Much of Deno’s public web services use Fresh (for example the site you are reading this blog post on now!). This does not mean we are done with Fresh. We have many more ideas to improve user and developer experience.
Try Deno Deploy - you will be surprised at the speed and simplicity.
Finally, thank you to Sylvain Cau, @hashrock, and Christian Norrman for your help on the Fresh project. Additional thanks to the Preact team for building Preact, and Jason Miller for his Islands Architecture blog post.