Deno Fresh Stack

Deno Fresh Stack

Fast SSR using Web Standards

🦖 What is Deno?

Deno is a JavaScript runtime just like Node.js or Bun. In fact Ryan Dahl who created Node.js (13 years ago), is behind Deno. Ryan Dahl considered there to be some shortcomings with Node.js. To be able to move quickly, he opted to write a fast, new alternative in Rust starting from a blank canvas, instead of forking and tweaking Node.js. Deno focusses on using the web platform and supports all widely used Web APIs. One big difference between Node.js and Deno is how imports work and it only recently added support for most NPM packages. That said the Deno Fresh stack will feel familiar if you have used any modern framework. My main stumbling block has been remembering to use the deno task start command instead of my trusted pnpm dev.

In fact that difference highlights another Deno feature — no separate dev and build steps. Deno generates your site (almost instantly) at run time which means no surprises when you deploy.

In an earlier post I talked about trying out Deno Fresh for the first time. Since then I have deployed my first production site so want to give an update on what I liked and any sharp corners I encountered. We start by taking a quick look at what Deno Fresh is before comparing it to some other Next Gen frameworks. Finally we end with what I liked and disliked most. Pop open the previous post if you are just looking to get started with Deno Fresh.

🥞 Deno Fresh CSS Stack

The site I built was for the online edition of an email newsletter. We use Deno Fresh to build the entire site and the issues themselves are authored in Markdown. For styling we use vanilla CSS with PostCSS to enable some future CSS proposals. The final component of this Deno Fresh stack is WASM modules written in Rust. These provider helper functions which:

  • parse the input Markdown for each issue into HTML to display on the page,
  • generate email HTML from the issue Markdown using MJML (modern HTML is not widely supported in HTML email clients so we need this extra processing),
  • finally the WASM module creates a plaintext version of the issue email (hard-wrapped to 72 characters per line). We send this in parallel with the HTML version ( as a multipart/alternative email).

Deno Fresh Stack: Screen capture shows a plaintext email open in a n email client. Text is in black monospace font on a white background and wrapped to a fixed line length

There is a Tailwind integration for the Deno Fresh stack, but I preferred plain old CSS for this project. Finally the stack includes the Upstash Redis caching service for Serverless apps.

🤔 Astro Builds Fast Websites too so how is Deno Fresh Different?

Deno Fresh builds Server-Side Rendered (SSR) sites and comes straight from the Deno team. It is fast, has no-fuss configuration and leans into Web APIs. That all probably sounds very familiar if you have tried out Astro. In fact, like Astro it also ships zero JavaScript by default and supports partial hydration.

Hydration is the process of loading the site’s JavaScript and making sure the state of the app on the user device is consistent. Typically for content-focussed sites (like blogs, documentation and brochure sites) much of the page is just text or images and there is not really any state to reconcile. Here only small “islands” of interactivity on the page will need hydration. Before partial hydration, builders shipped JavaScript code to manage hydration of even these islands (which did not need it). Partial hydration is an optimisation which lets you choose which parts to hydrate.

Islands of Interactivity

Deno Fresh Stack: Screen capture shows a web page in Firefox with the NoScript plugin. The url is redacted. There is no page content rendered just back text reading 'Please enable Java Script to run this app' on a white background.

As an example, on out Deno Fresh stack site, we wanted to add a share button using the WebShare API. This lets the device choose installed apps to present to the user for sharing your content. The API needs spot of JavaScript, on top, at the moment it is only supported on modern Android, iOS for mobile and Safari on Desktop. I needed to integrate a graceful degradation fallback for unsupported devices. This is the perfect use case for an island. On top it lets us keep the rest of the page JavaScript free. In fact people who generally prefer to keep JavaScript switched off for security and privacy reasons can leave JavaScript disabled, read the whole page and even subscribe to the newsletter!

Deno Fresh Stack: two adjacent screen captures show the same site, one with a Web Share icon, the other with separate Twitter, Whats App and Telegram icons.

Anyway, with Fresh or Astro you can add a little logic like this in your island component and you are at the races:

  // assume WebShare is supported initially
  const [webShareAPISupported, setWebShareAPISupported] = useState<boolean>(
    true,
  );

  useEffect(() => {
        /* Typically you can detect WebShare support by checking `navigator.share`
         * is defined. If it is not defined we can update state and show
         * old school share buttons.
         */
    if (typeof navigator.share === "undefined") setWebShareAPISupported(false);
  }, [webShareAPISupported]);

That code is written in Preact — this is a major difference with Astro. Astro lets you write code in Svelte and other frameworks (including the Preact library). Preact recognises React syntax and has some extra features on top, like Signals which are lovely for sharing state between components. TypeScript support comes out of the box (no more having to remember what you finally had to add to tsconfig.json in your last Node.js project to get ESLint to work properly with TypeScript 😅). On top Deno has integrated testing and linting tooling.

Deno Fresh Stack Routing Differences

Routing is not too different between Astro and Fresh. The main difference is that Preact lets you add in _middleware.ts files. These are nice for example to add HTTP security headers to all routes from one place.

🤷🏽 Remix and SvelteKit use Web Standards what's wrong with those?

Remix is built around React router. This can be very convenient, especially for nested routes. If you have a large site with a lot of nested components, Remix is probably your framework! Without heavy nesting though, you might find Fresh a lighter option.

Deno Fresh Stack: Screen capture shows the output from the sub font tool. Although it increases HTML/CSS by 61.5 kB, by reducing font bytes shipped, net it saves 379 kB

While SvelteKit handles CSS scoping for you, you are on your own in Remix and Fresh. That said I really enjoyed the flexibility I had setting up PostCSS CLI to process the CSS input. Optimising was a breeze. Fresh gives you back more control. This control helps when you use a tool like subfont to minimise the number of bytes you ship to the user when you self-host fonts. subfont scans your pages and looks for glyphs (essentially characters or character/accent combinations like é, ê and so on) which you do not use anywhere but would otherwise be shipped with the font files. You will be surprised how many bytes you can save by running it, especially for font family/weight combinations you just use in titles, for example. On our Deno Fresh stack site, we save almost 400 kB — a mobile user on a slow connection will appreciate that!

🍋 What else I like about the Deno Fresh Stack

We already looked at some of the batteries included features of Deno Fresh, like TypeScript, Linting and Testing. We also mentioned speed once or twice, Here are a few more stand-out features to wrap-up:

  • instant build: using Deno Deploy, essentially I hit enter on git push in the console, hit refresh on my browser and whatever changes I make are already live! It wasn’t too long ago that I was working with a static site builder on a content site which took over half an hour to build in the cloud. This was before Vite and even changes on the local dev server probably weren’t that quick! Because you can run PageSpeed Insights that quickly, you can chip away at optimisation and make sure you see the changes expected.
  • more control: I mentioned this before talking about subfont optimisations. You can also optimise CSS by separating out non-critical CSS (for example hover styles on a link or desktop styles when the user is on mobile) and defer loading it. This is CSS which can be skipped initially in order to get the page displayed as quickly as possible. The extra control you have with Deno also makes this kind of optimisation more straightforward.
  • Rust WASM - Deno has nice WASM integrations with the wasmbuild package. I am learning Rust and try to shoehorn in a bit of Rust wherever I can! I created some WASM functions in Rust to parse Markdown input and add generate HTML, add id anchor links to headings, generate an in-article navigation menu and also estimate reading time. This is all wrapped into the parsedown GitHub repo.

🫤 What I think is still missing from Fresh

  • I love styling with vanilla-extract. It would be nice to have that working with Deno Fresh. I know Mark Dalgleish is working on Remix integration and there might be some overlap which can be leveraged.
  • instead of a package.json listing dependencies, with Deno you can use an import map in file. This lets you import packages using URLs (not just NPM). I like being able to run pnpm outdated to check which packages need updating when working in SvelteKit or Astro and haven’t yet found a Deno tool to do this yet.
  • Deno has a built-in formatter which saves you having to configure Prettier on each project. It also has a VSCode integration. I have set this to format code on each save, but see small discrepancies between the VSCode plugin saved code and how the CLI tool formats. I guess teething issues like this are to be expected and this will son be ironed out.

To be honest overall I cannot really complain about Deno. In particular, I like Web API support. I added AES encryption to an app in 30 minutes with the WebCrypto API. I have built a couple of other sites which run locally to help with various tasks and they all run smoothly. Of course, I coded up Rust WASM modules in each of them 😅.

🙌🏽 Deno Fresh Stack: Wrapping Up

Take a look at the new Fresh Rodney Lab Newsletter site and drop a comment below with your feedback. It scored pretty well on Lighthouse initially and I tweaked a little more just because sites never maintain their initial score 😅. Let me know if you want to hear more details on the optimisations implemented.

Here we have taken a quick look at what Deno Fresh brings to the table. In particular, we saw:

  • how Fresh compares to Astro, Remix and SvelteKit,
  • what Partial Hydration is and when it might be useful,
  • how Fresh is optimisation friendly.

Check out the Fresh docs for further details. Get in touch if you would like to see more content on Deno and Fresh. Also keen to know if you are learning Rust too or just like WASM. Where have you taken WASM and Deno? I hope you found the content useful and am keen to hear about possible improvements.

🙏🏽 Deno Fresh Stack: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, then please consider supporting me through Buy me a Coffee.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter, @rodney@toot.community on Mastodon and also the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as SvelteKit. Also subscribe to the newsletter to keep up-to-date with our latest projects.

Did you find this article valuable?

Support Rodney Lab - Content Site WebDev & Serverless by becoming a sponsor. Any amount is appreciated!