Skip to main content
Deno 2 is finally here 🎉️
Learn more
Fresh lemon

Fresh 1.5: Partials, client side navigation and more

Today, we’re happy to announce the 1.5 release of Fresh, the fast, Deno-native framework for building full stack web applications.

This release contains a brand new approach to client-side navigation that we’re calling Partials. Using HTML attributes, you can configure your Fresh apps to replace HTML in an already-loaded page using server-rendered markup, without a page reload. This style of navigation makes your app feel much more responsive, and prevents losing state in your client-side island components across page turns.

In addition to Partials, Fresh 1.5 contains a number of improvements and bug fixes aimed at supporting complex UI development patterns.

Ready to try it out? You can start a new Fresh project by running this command:

deno run -Ar https://fresh.deno.dev

You can also update an existing project by running the following command in your project folder:

deno run -Ar https://fresh.deno.dev/update .

Here’s an overview of what’s new in 1.5. Read on for all the details, plus what to expect in the next iteration of Fresh.

Fresh 1.5 at a glance

Client side navigation with partials

Traditionally, client side navigation is a tricky thing to get right in web development. It usually requires lots of boilerplate and a new data loading strategy. For Fresh we wanted to solve this in a way that doesn’t require developers to change how they build websites and applications.

With Fresh 1.5 we’re releasing a new concept called Partials that makes Fresh apps feel more app-like by blending client and server-side navigation. The idea behind Partials is that you can mark areas in your page that change on navigation and Fresh will only update those. And the best part: Island state is kept intact.

Notice how the search bar island on the Fresh documentation page doesn’t flicker anymore when navigating between pages.

To make this work, we only needed to change two things in our code:

  1. Add the f-client-nav attribute to a container element to make any links below that node opt into client side navigation and partials
  2. Wrap the main content area with a <Partial name="content">...</Partial> component

And boom! We now have full client side navigation in our documentation! The full changes can be seen in this PR.

- import { asset, Head } from "$fresh/runtime.ts";
+ import { asset, Head, Partial } from "$fresh/runtime.ts";

  // ...snip

  export default function DocsPage(props: PageProps<Data>) {
    return (
-     <div class="flex flex-col min-h-screen">
+     <div class="flex flex-col min-h-screen" f-client-nav>
-       <Content page={props.page} />
+       <Partial name="docs-main">
+         <Content page={props.page} />
+       </Partial>
      </div>
    )
  }

Behind the scenes, Fresh fetches the new page and only pulls out the relevant content out of the HTML response.

Granular partial modifications

We can optimise this pattern even further by opting into more granular partial modifications with the f-partial attribute.

- <a href="/docs/routes">Routes</a>
+ <a href="/docs/routes" f-partial="/partials/docs/routes">Routes</a>

When the link is clicked, we’ll navigate to /docs/routes as expected, but fetch the new content from /partials/docs/routes instead. In our case this can be a slimmed down HTML page that only returns the main content and bypasses rendering the outer document directly on the server.

export default function DocRoute() {
  return (
    <Partial name="content">
      {/* Render only the markdown content here */}
    </Partial>
  );
}

Applying multiple Partials in one single HTTP response

A neat aspect of partials in Fresh is that a response can return as many partials as desired. That way you can update multiple unrelated areas on your page in one single HTTP response. A scenario where this is useful are online shops for example.

export default function AddToCart() {
  return (
    <>
      <Partial name="cart-items" mode="append">
        {/* Render the new cart item here */}
      </Partial>
      <Partial name="total-price">
        <p>Total: {totalPrice}</p>
      </Partial>
    </>
  );
}

Specifying the replacement mode

You may have noticed in the previous example that we used a new mode="append" prop on the <Partial> component. The default mode is to always replace the contents of a partial, but with the mode prop you can specify how you’d like the content to be integrated into the active page. We’ve added three distinct merge modes:

  • replace - Swap out the content of the existing partial (default)
  • prepend - Insert the new content before the existing content
  • append - Insert the new content after the existing content

Personally, we’ve found that the append mode is really useful when you have an UI which displays log messages or similar list-like data. Head on over to our documentation to learn more about Partials.

One thing we always wanted to make easier is to style active links. Most code bases we have seen so far forward the current url through a lot of components to be able to check if the href attribute of an <a>-element should be styled in a way to show that it’s the current page.

The thing is that Fresh already knows what the current URL is and can do this automatically for you. No need to manually pass along the current url. With Fresh 1.5 we’ll add the following two attribute to links:

  • data-current - Added to links with an exact path match
  • data-ancestor - Added to links which partially match the current URL

Here is an example of how these can be styled with CSS:

/* Give links pointing to the current page a green color */
a[data-current] {
  color: green;
}

/* Color all ancestor links of the current page the color peachpuff */
a[data-ancestor] {
  color: peachpuff;
}

…and with twind:

// Current link
<a href="/foo" class="[data-current]:text-green-600">...</a>

// Ancestor link
<a href="/foo" class="[data-ancestor]:font-bold">...</a>

Custom build targets

Internally, Fresh passes the generation of optimized frontend assets to esbuild , which has a pretty cool “target” feature which allows you to specify the minimum browser versions you want to support. esbuild takes that and tries to convert newer JavaScript construct not supported in the specified range to something these older engines support. Since every project has different requirements, it was long overdue to expose that in our configuration.

// fresh.config.ts
export default defineConfig({
  build: {
    target: ["chrome99", "firefox99", "safari15"],
  },
});

Head over to the esbuild documentation for a full list of possible target values.

Analyzing bundle files

With Fresh 1.5 we expose esbuild’s metafile.json file which can be used to analyze and inspect what modules are actually shipped to the browser. This file can be generated by running the build task of your project.

After the build is finished, you can inspect the meta file on https://esbuild.github.io/analyze/ which is provided by the esbuild project itself.

Thanks to Tiago Gimenes for adding this to Fresh!

Bar chart of the modules included in the assets for the fresh website.

Error overlay

We’ve spent a bit of time improving the developer experience as well. Fresh 1.5 will now render a proper error overlay and try its best to show you where the error originated from. Rest assured, this is only visible in when you run dev.ts.

The error overlay in browser shows you an excerpt of where it occured, the stack trace, as well as the error message.

Errors shown in the terminal will show you a visual indicator of where the error happened as well:

The terminal prints an excerpt of the surrounding area of where the stack trace points to.

It’s a minor thing, but is has proven already useful for myself working on Fresh directly.

Other noteworthy features

  • Inline scripts automatically get a nonce value, which makes using Content-Security Policy headers nicer
  • The Deno KV OAuth plugin was moved into the Fresh repository as it’s a first class supported plugin - Thanks to Asher Gomez and Michael Herzner for adding this!
  • The plugin API received a buildStart and buildEnd hook that is called when building respectively. We consider these two experimental as we’ll start exposing more things in the plugin API in the very near future and these might change.

What’s on the horizon?

With support for partials, we’re very close to shipping the View Transitions API as well. We’re also eager to make partials even more awesome! Other areas we’re thinking is new additions to our plugin API and much more.

Like in the past cycles you can follow the next iteration plan on GitHub.