Building Production Apps with Deno Fresh 2
I've been following along with the Fresh web app framework since I first learned about Deno a couple of years ago. Its promise is quite appealing: minimal config, Preact, use TypeScript without a compilation step, ship minimal JavaScript to the client.
In summer 2024 I built a web app powered by the first version of Fresh that was essentially a bespoke petition app. The development experience was quite nice—fast and easy to work with. But there was one bummer: hot module reloading (HMR) didn't work. Each change would result in a full page refresh, losing any state. This is a bit of a deal breaker, especially when working on polished user interfaces.
In the time since that project, I did a lot of experimenting with using Deno with Vite and Preact without Fresh, basically building client-side single page apps for projects where that architecture makes sense. It was a positive experience, but not viable for all my needs where I'd prefer server-side rendering for more content-heavy pages and experiences. So I kept my eye on Fresh's development, hoping for improvements.
When Fresh 2 was announced in early September to be in Beta, I was super excited. The announcement came with HMR and faster boot times as the key features, which was enough to get me to jump back in and try it out.
I've built a few things so far with it:
- brettchalupa.com - my website, migrated from about a decade of Jekyll to Fresh
- StoryHub - a story authoring platform for writing and organizing creative fiction
- Rex - a URL shortener for managing links in my books and other projects
- Prole - a service for provisioning Faktory instances
I've been writing some code from scratch, some with Claude Code. And I'm quite
happy with how it's going! There's been the occasional hiccup, like the dev
server crashing due to temporary files (which I
opened a PR to fix), but overall
it feels quite stable. Also,
using the Stripe package requires a hacky workaround right now.
I'm excited to keep building with it as my project ideas grow. Claude Code is
able to contribute productively, with Deno's built-in commands like deno lint,
deno fmt, deno check, and deno test giving quick feedback for rapid
iteration and ensuring quality.
I've used it both with Tailwind and just vanilla CSS for styles. Preact is much like React and easy enough to pick up on if you're familiar with React. I've written tests using Deno's testing functionality at the integration and unit level, which has been straightforward and pleasant.
There's just something really appealing to me about using Deno and Fresh contrasted to my day job where I'm using Node.js with TypeScript and Next.js with a bunch of other ancillary dependencies. Deno and Fresh are simple, which is mighty refreshing.
It's a bit surprising to me that at the time of this writing, Fresh 2 includes middleware plugins for CORS, CSRF, and CSP, but they aren't included and enabled by default (docs). This feels like an area where secure defaults would be valuable, even if you can opt out.
What I Love
- Hot module reloading that actually works - The main reason I came back to Fresh 2, and it delivers. State persists between changes, making UI work a breeze.
- Minimal configuration - Projects get up and running quickly without wrestling with build tools or configs.
- Built-in tooling - Deno's baked-in linting, formatting, and testing tools mean fewer dependencies to manage.
- Fast deploys - Easy setup on Deno Deploy with incredibly fast deployment times.
- TypeScript without the hassle - No compilation step, no tsconfig headaches, just write TypeScript and go.
What's Missing
- End-to-end testing tooling - While unit and integration testing work well with Deno's built-in test runner, I haven't explored how well tools like Playwright integrate yet. This is an area I'd like to explore more.
- Ecosystem maturity - The Fresh ecosystem is still young compared to Next.js or other established frameworks. Fewer ready-made solutions and examples to reference.
- Robust logging - Every web app needs logging, and I wish Fresh 2 came with a more robust logger that one could opt out of but was set up by default.
- Documentation - The docs are okay right now, but they're a bit thin on more advanced uses, like how to use Fresh 2 in a Deno Workspace. I intend to work on improving the docs.
Idea: A Template
There aren't a ton of features I think Fresh 2 needs. Sure, static routes at build time would be nice, but that can be worked around with caching.
I do think there are two major things missing for Fresh though:
- Extensive documentation and tutorials, which I'm interested in helping contribute to.
- A template project showing what a robust, production-grade Fresh app is like.
I'm quite interested in creating this template project, given I've been building a variety of applications with Fresh 2 and see patterns emerging. Having a well-documented reference implementation would help newcomers get started faster and demonstrate best practices. The template would include:
- Database integration with Postgres
- Authentication (both traditional and OAuth)
- Reusable UI components
- Tailwind for styling
- Common utility helpers
- Security middleware (CORS, CSRF, CSP) enabled by default
- Structured logging
- Background job processing
- Email sending capabilities
- Comprehensive tests
While the Deno SaaSKit exists, it's built with Fresh 1 and relies solely upon OAuth. Having a Fresh 2 template with multiple auth options and modern patterns would fill a valuable gap in the ecosystem.
Should You Use It?
If you're starting a new project and want a refreshingly simple development experience, I'd recommend giving Fresh 2 a try. It's especially well-suited for content-heavy sites, small-to-medium web apps, and side projects where you want to focus on building features rather than configuring tools.
The lack of HMR was a dealbreaker in Fresh 1, but with Fresh 2 addressing that pain point, it's become a genuinely enjoyable framework to work with. Combined with Deno's excellent developer experience and the power of Preact, Fresh 2 feels like a breath of fresh air in the web development landscape.
I'm looking forward to building more with it and seeing where the framework goes as it continues to mature.