Duck 2.4.0 Takes Flight: Native Apps, Smarter Caching, and Slicker Components
Duck FrameworkPython web frameworkrelease notesDuck NativeLivelyHTTP caching

Duck 2.4.0 Takes Flight: Native Apps, Smarter Caching, and Slicker Components


Duck Framework 2.4.0 is here, released October 27, 2026. This is our biggest update yet for the Python web framework: the first step toward Duck Native, faster and smarter HTTP caching, a richer component library, and a long list of reliability fixes.

Upgrade now and see what’s new.

The Big Wins

  • Duck Native (first stage): manage native apps with the new duck native command.
  • Smarter HTTP caching: ETag, Last-Modified, 304 Not Modified, and If-Range support.
  • Click effects out of the box: new RippleExtension and HighlightExtension.
  • New components: LinkButton and IconButton.
  • Production-aware minification for JS, CSS, and HTML.

Duck Spreads Its Wings: Native Apps Are Coming

Web apps are only the start. Duck 2.4.0 takes the first stage toward Duck Native and ships the duck native command for managing native apps from the same toolchain you already use. Expect much more here in upcoming releases.

Cache Smarter, Serve Faster

Static files and downloads now play nicely with browser and CDN caches.

  • FileIOStream.etag and FileIOStream.last_modified come from a single os.stat() call (st_size and st_mtime_ns), so cache validators are generated without reading file content.
  • If-None-Match and If-Modified-Since are handled automatically. Cacheable GET/HEAD responses are downgraded to 304 Not Modified when appropriate.
  • If-Range is now supported for byte-range requests. A range is only honored if it matches the current strong ETag (or Last-Modified as a fallback). If the file changed, Duck serves the full resource, which prevents corrupted partial downloads.

The result: less bandwidth, faster page loads, and more reliable resumable downloads.

Clicks That Feel Alive: Ripple and Highlight

Clickable components get a new extension system under duck.html.components.extensions.clickable:

  • RippleExtension: a material-style ripple on click.
  • HighlightExtension: now applied to all clickable components by default.

Don’t want the effect? Opt out per component:

from duck.html.components.extensions.clickable import HighlightExtension

Button(exclude_extensions=[HighlightExtension])

You can also register an extension globally for a component class with register_default_extension from duck.html.components.extensions.

A Fresh Flock of Components

  • LinkButton (duck.html.components.link) and IconButton (duck.html.components.icon) are new.
  • Code is now CodeBlock, with new filename and collapsible arguments.
  • Select accepts a list or tuple for options, in addition to a mapping.
  • Newlines in text: HTML components’ text property now supports \n, rendered as a line break.
  • Footer has improved mobile positioning and cleaner UI logic.
  • Page gains set_webmanifest for easier PWA setup.

Lively Stays Fresh, Synced, and Secure

  • New ensure_browser_state_synced on the Lively WebSocket lets you easily and securely sync cookies and other state with the browser.
  • Page components now refresh automatically when they expire or are no longer registered, so users always get fresh content.
  • add_to_registry is no longer strict about request for ownership assignment. Missing values are flagged lazily on Lively event dispatch.

Lean in Production, Friendly in Debug

JS, CSS, and HTML minification is now enabled by default only in production mode, so debugging stays pleasant.

Setting Purpose
COMPONENTS_MINIFY_JS Control JS minification
COMPONENTS_MINIFY_CSS Control CSS minification
COMPONENTS_MINIFY_HTML Control HTML minification
COMPONENTS_FORCE_MINIFY_* Force minification in debug mode

HTML minification comes from the new duck.html.html_minify module and applies to components that accept inner HTML.

Little Upgrades, Big Smiles

  • duck.toml now supports packages with versions.
  • New read_chunked(), write_chunked(), async_read_chunked(), and async_write_chunked() helpers.
  • New Spacemail provider in duck.utils.email for sending email through Spacemail.
  • collect_email has a new failsafe argument that logs exceptions instead of raising them.

Squashed Bugs

  • Fixed infinite session expired loops during component and page auto-recovery.
  • Fixed scrolling to elements when Lively navigates to URLs with hashes.
  • FileIOStream.read() and AsyncFileIOStream.read() now honor the requested size. Previously they returned at most one chunk, and the short result was cached under the full-size key.
  • write() now writes in chunk_size pieces until all data is written, and the cache and on_write hooks reflect the bytes actually written.
  • A write that fails midway returns the partial byte count. A failure on the first chunk still raises OSError.
  • Negative size values now read the whole file, matching Python’s file API.
  • Fixed MCPView’s run() bug.
  • Fixed a BasicExtension linebreak replacement bug in the inner_html setter.
  • Fixed StreamingRangeHttpResponse parsing the Range header incorrectly, which caused slightly wrong data.

Dive In: Upgrade Today

pip install --upgrade duck

Review your minification settings and any use of the old Code component (now CodeBlock) before deploying.

Where the Pond Leads Next

Duck Native is just getting started. Follow the Duck Framework blog for updates, and tell us what you want to see next. Check out this repo for more info on Duck Native.

Happy building with Duck!