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
- ⚡️ Faster client-side navigation with Partials Replace HTML in a page with partial server-rendered HTML. Granular control over what markup is replaced, and support for returning multiple partials in a single request.
- 💅 Easier active link styling Fresh will add HTML attributes to links that could be styled differently to represent a current page or section, a common web app feature.
- 🛠️ Custom esbuild targets Configure frontend build targets for esbuild, allowing you more granular control over browser support.
-
🕵️♀️
Analyze bundle files
Using the newly exposed
metafile.json
from esbuild, you can analyze the contents of your frontend build visually. - 🪲 New error overlay You don’t often introduce bugs, but when you do - we’ve made them easier to read.
- 🏝️ Quality of life improvements & bug fixes A few small improvements we hope you’ll enjoy.
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.
To make this work, we only needed to change two things in our code:
- Add the
f-client-nav
attribute to a container element to make any links below that node opt into client side navigation and partials - 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 contentappend
- 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.
Easier active link styling
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 matchdata-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!
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
.
Errors shown in the terminal will show you a visual indicator of where the error happened as well:
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
andbuildEnd
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.