diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -23,6 +23,29 @@
 See also the hledger changelog.
 
 
+# 1.52.4 2026-09-10
+
+Fixes
+
+- Raise aeson's lower bound to `>=2.2.5.1`, avoiding versions vulnerable to denial-of-service.
+  (<https://haskell.github.io/security-advisories/advisory/HSEC-2026-0007.html>)
+
+Improvements
+
+- The yesod-static and hjsmin dependencies have been dropped;
+  hledger-web now serves its static files itself, using wai-app-static
+  and file-embed. (yesod-static doesn't currently build with crypton
+  1.1+, which has kept it, and hledger-web, out of stackage nightly.)
+  Static file urls no longer include an `?etag=...` cache buster;
+  instead the files are served with an ETag header, and conditional
+  requests are answered with 304 Not Modified.
+  (Cherry picked from an AI-assisted change in hledger 2.x.)
+
+- Allow megaparsec 9.8.1+ (but not 9.8.0, because of [megaparsec#572](https://github.com/mrkkrp/megaparsec/issues/572)).
+
+- Allow yesod-core 1.7.0.0.
+
+
 # 1.52.3 2026-08-27
 
 Fixes
@@ -45,11 +68,7 @@
   https://hledger.org/AI.html; they have been reviewed and tested.
 
 - hledger-web's official binaries, and builds from the hledger source
-  tree, now use aeson 2.3, avoiding a denial of service bug in that
-  library. (With older aeson and /add enabled, hledger-web is
-  vulnerable to HTTP requests which can trigger memory/CPU exhaustion.)
-  aeson 2.3 is not yet in stackage, so hledger-web installed from
-  hackage will normally still use the older aeson.
+  tree, now use aeson 2.3, avoiding a denial of service bug.
   (<https://haskell.github.io/security-advisories/advisory/HSEC-2026-0007.html>)
 
 [#2700]: https://github.com/simonmichael/hledger/issues/2700
diff --git a/Hledger/Web/App.hs b/Hledger/Web/App.hs
--- a/Hledger/Web/App.hs
+++ b/Hledger/Web/App.hs
@@ -5,7 +5,6 @@
 -}
 
 {-# OPTIONS_GHC -fno-warn-orphans  #-}
-{-# LANGUAGE CPP                   #-}
 {-# LANGUAGE FlexibleInstances     #-}
 {-# LANGUAGE LambdaCase            #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -37,15 +36,8 @@
 import Text.Blaze (Markup)
 import Text.Hamlet (hamletFile)
 import Yesod
-import Yesod.Static
 import Yesod.Default.Config
 
-#ifndef DEVELOPMENT
-import Hledger.Web.Settings (staticDir)
-import Text.Jasmine (minifym)
-import Yesod.Default.Util (addStaticContentExternal)
-#endif
-
 import Hledger
 import Hledger.Cli (CliOpts(..), journalReloadIfChanged)
 import Hledger.Web.Settings (Extra(..), widgetFile)
@@ -60,7 +52,7 @@
 -- access to the data present here.
 data App = App
     { settings :: AppConfig DefaultEnv Extra
-    , getStatic :: Static -- ^ Settings for static file serving.
+    , getStatic :: WaiSubsite -- ^ The static file serving site (see StaticFiles.hs).
     , httpManager :: Manager
       --
     , appOpts    :: WebOpts
@@ -188,15 +180,6 @@
       $(widgetFile "default-layout")
 
     withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
-
--- XXX why disabled during development ? Affects ghci, ghcid, tests, #2139 ?
-#ifndef DEVELOPMENT
-  -- This function creates static content files in the static folder
-  -- and names them based on a hash of their content. This allows
-  -- expiration dates to be set far in the future without worry of
-  -- users receiving stale content.
-  addStaticContent = addStaticContentExternal minifym base64md5 staticDir (StaticR . flip StaticRoute [])
-#endif
 
 -- This instance is required to use forms. You can modify renderMessage to
 -- achieve customized and internationalized form validation messages.
diff --git a/Hledger/Web/Settings.hs b/Hledger/Web/Settings.hs
--- a/Hledger/Web/Settings.hs
+++ b/Hledger/Web/Settings.hs
@@ -7,16 +7,21 @@
 
 {-# LANGUAGE CPP               #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell   #-}
 
 module Hledger.Web.Settings where
 
+import Control.Monad (unless)
 import Data.Default (def)
 import Data.Maybe (fromMaybe)
 import Data.Text (Text)
 import Data.Text qualified as T
 import Data.Yaml
-import Language.Haskell.TH.Syntax (Q, Exp)
+import Language.Haskell.TH.Syntax (Q, Exp, runIO)
+import System.Directory (doesFileExist)
+import System.FilePath ((</>))
 import Text.Hamlet
+import Yesod.Core.Types (Route(WaiSubsiteRoute))
 import Yesod.Default.Config
 import Yesod.Default.Util
 
@@ -77,6 +82,16 @@
 -- XXX Does not respect --file-url #2139
 staticRoot :: AppConfig DefaultEnv Extra -> Text
 staticRoot conf = fromMaybe (appRoot conf <> "/static") . extraStaticRoot $ appExtra conf
+
+-- | Make a type-safe URL for a file within the static directory,
+-- given its path relative to that directory (using / separators).
+-- Checks at compile time that the file exists.
+staticFileRoute :: FilePath -> Q Exp
+staticFileRoute f = do
+  let path = staticDir </> f
+  exists <- runIO $ doesFileExist path
+  unless exists . fail $ "staticFileRoute: " ++ path ++ " not found"
+  [| WaiSubsiteRoute (T.splitOn (T.singleton '/') (T.pack f)) [] |]
 
 -- | Settings for 'widgetFile', such as which template languages to support and
 -- default Hamlet settings.
diff --git a/Hledger/Web/Settings/StaticFiles.hs b/Hledger/Web/Settings/StaticFiles.hs
--- a/Hledger/Web/Settings/StaticFiles.hs
+++ b/Hledger/Web/Settings/StaticFiles.hs
@@ -1,31 +1,50 @@
 {-# LANGUAGE TemplateHaskell #-}
+{-|
+Serving of the web app's static files, and type-safe routes for them.
+-}
 module Hledger.Web.Settings.StaticFiles where
 
+import Data.FileEmbed (embedDir)
+import Network.Wai.Application.Static (defaultFileServerSettings, embeddedSettings, staticApp)
 import System.IO (stdout, hFlush)
-import Yesod.Static (Static, embed, publicFiles, staticDevel)
-
-import Hledger.Web.Settings (staticDir, development)
-
--- | use this to create your static file serving site
--- staticSite :: IO Static.Static
--- staticSite = if development then Static.staticDevel staticDir
---                             else Static.static      staticDir
---
--- | This generates easy references to files in the static directory at compile time,
---   giving you compile-time verification that referenced files exist.
---   Warning: any files added to your static directory during run-time can't be
---   accessed this way. You'll have to use their FilePath or URL to access them.
--- $(staticFiles Settings.staticDir)
+import WaiAppStatic.Types (StaticSettings(..))
+import Yesod.Core (WaiSubsite(..))
 
+import Hledger.Web.Settings (staticDir, development, staticFileRoute)
 
-staticSite :: IO Static
+-- | Create the static file serving site (a WAI app used as a Yesod subsite).
+-- In development builds it serves the files in the static directory,
+-- re-reading them from disk on each request;
+-- otherwise it serves the static directory's files as they were
+-- at compile time, embedded in the executable.
+staticSite :: IO WaiSubsite
 staticSite =
   if development
-   then (do
-            putStrLn ("Running in dev mode, will read static files from " ++ staticDir ++ "/") >> hFlush stdout
-            staticDevel staticDir)
-   else (do
-            -- putStrLn "Using built-in web files" >> hFlush stdout
-            return $(embed staticDir))
+   then do
+     putStrLn ("Running in dev mode, will read static files from " ++ staticDir ++ "/") >> hFlush stdout
+     return $ serve $ defaultFileServerSettings staticDir
+   else
+     -- putStrLn "Using built-in web files" >> hFlush stdout
+     return $ serve $ embeddedSettings $(embedDir staticDir)
+  where
+    -- serve files with hash-based ETag headers, so browsers can cache them
+    serve settings = WaiSubsite $ staticApp settings{ssUseHash = True}
 
-$(publicFiles staticDir)
+-- Type-safe routes for the static files used by the app,
+-- verified to exist at compile time.
+css_bootstrap_min_css                       = $(staticFileRoute "css/bootstrap.min.css")
+css_bootstrap_datepicker_standalone_min_css = $(staticFileRoute "css/bootstrap-datepicker.standalone.min.css")
+js_bootstrap_min_js                         = $(staticFileRoute "js/bootstrap.min.js")
+js_bootstrap_datepicker_min_js              = $(staticFileRoute "js/bootstrap-datepicker.min.js")
+js_excanvas_min_js                          = $(staticFileRoute "js/excanvas.min.js")
+js_jquery_cookie_js                         = $(staticFileRoute "js/jquery.cookie.js")
+js_jquery_flot_min_js                       = $(staticFileRoute "js/jquery.flot.min.js")
+js_jquery_flot_selection_min_js             = $(staticFileRoute "js/jquery.flot.selection.min.js")
+js_jquery_flot_time_min_js                  = $(staticFileRoute "js/jquery.flot.time.min.js")
+js_jquery_flot_tooltip_min_js               = $(staticFileRoute "js/jquery.flot.tooltip.min.js")
+js_jquery_hotkeys_js                        = $(staticFileRoute "js/jquery.hotkeys.js")
+js_jquery_min_js                            = $(staticFileRoute "js/jquery.min.js")
+js_jquery_url_js                            = $(staticFileRoute "js/jquery.url.js")
+js_typeahead_bundle_min_js                  = $(staticFileRoute "js/typeahead.bundle.min.js")
+hledger_css                                 = $(staticFileRoute "hledger.css")
+hledger_js                                  = $(staticFileRoute "hledger.js")
diff --git a/config/routes b/config/routes
--- a/config/routes
+++ b/config/routes
@@ -1,6 +1,6 @@
 /favicon.ico     FaviconR        GET
 /robots.txt      RobotsR         GET
-/static          StaticR         Static getStatic
+/static          StaticR         WaiSubsite getStatic
 
 /openapi.json    OpenApiR        GET
 
diff --git a/hledger-web.1 b/hledger-web.1
--- a/hledger-web.1
+++ b/hledger-web.1
@@ -1,5 +1,5 @@
 
-.TH "HLEDGER\-WEB" "1" "August 2026" "hledger-web-1.52.3 " "hledger User Manuals"
+.TH "HLEDGER\-WEB" "1" "August 2026" "hledger-web-1.52.4 " "hledger User Manuals"
 
 
 
@@ -17,7 +17,7 @@
 .PD
 \f[CR]hledger web [OPTS] [QUERY]\f[R]
 .SH DESCRIPTION
-This manual is for hledger\(aqs web interface, version 1.52.3.
+This manual is for hledger\(aqs web interface, version 1.52.4.
 See also the hledger manual for common concepts and file formats.
 .PP
 hledger is a robust, user\-friendly, cross\-platform set of programs for
diff --git a/hledger-web.cabal b/hledger-web.cabal
--- a/hledger-web.cabal
+++ b/hledger-web.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hledger-web
-version:        1.52.3
+version:        1.52.4
 synopsis:       Web user interface for the hledger accounting system
 description:    A simple web user interface for the hledger accounting system,
                 providing a more modern UI than the command-line or terminal interfaces.
@@ -154,10 +154,10 @@
   hs-source-dirs:
       ./
   ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind -threaded
-  cpp-options: -DVERSION="1.52.3"
+  cpp-options: -DVERSION="1.52.4"
   build-depends:
       Decimal >=0.5.1
-    , aeson >=1 && <2.4
+    , aeson >=2.2.5.1 && <2.4
     , base >=4.18 && <4.23
     , base64
     , blaze-html
@@ -175,14 +175,13 @@
     , file-embed >=0.0.16.0
     , filepath
     , githash >=0.1.6.2
-    , hjsmin
-    , hledger >=1.52.3 && <1.53
-    , hledger-lib >=1.52.3 && <1.53
+    , hledger >=1.52.4 && <1.53
+    , hledger-lib >=1.52.4 && <1.53
     , hspec
     , http-client
     , http-conduit
     , http-types
-    , megaparsec >=7.0.0 && <9.8
+    , megaparsec >=7.0.0 && <9.8 || >=9.8.1 && <9.9
     , mtl >=2.2.1
     , network
     , safe >=0.3.20
@@ -195,15 +194,15 @@
     , unordered-containers
     , utf8-string
     , wai
+    , wai-app-static >=3.1
     , wai-cors
     , wai-extra
     , wai-handler-launch >=3.0.3
     , warp
     , yaml
     , yesod >=1.4 && <1.7
-    , yesod-core >=1.4 && <1.7
+    , yesod-core >=1.4 && <1.8
     , yesod-form >=1.4 && <1.8
-    , yesod-static >=1.4 && <1.6.1.1
     , yesod-test
   default-language: GHC2021
   if (flag(dev)) || (flag(library-only))
@@ -222,7 +221,7 @@
   hs-source-dirs:
       app
   ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind -threaded
-  cpp-options: -DVERSION="1.52.3"
+  cpp-options: -DVERSION="1.52.4"
   build-depends:
       base >=4.18 && <4.23
     , hledger-web
@@ -242,7 +241,7 @@
   hs-source-dirs:
       test
   ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind -threaded
-  cpp-options: -DVERSION="1.52.3"
+  cpp-options: -DVERSION="1.52.4"
   build-depends:
       base >=4.18 && <4.23
     , hledger-web
diff --git a/hledger-web.info b/hledger-web.info
--- a/hledger-web.info
+++ b/hledger-web.info
@@ -18,7 +18,7 @@
 or
 'hledger web [OPTS] [QUERY]'
 
-   This manual is for hledger's web interface, version 1.52.3.  See also
+   This manual is for hledger's web interface, version 1.52.4.  See also
 the hledger manual for common concepts and file formats.
 
    hledger is a robust, user-friendly, cross-platform set of programs
diff --git a/hledger-web.txt b/hledger-web.txt
--- a/hledger-web.txt
+++ b/hledger-web.txt
@@ -11,7 +11,7 @@
      hledger web [OPTS] [QUERY]
 
 DESCRIPTION
-     This manual is for hledger's web interface, version 1.52.3.  See  also  the
+     This manual is for hledger's web interface, version 1.52.4.  See  also  the
      hledger manual for common concepts and file formats.
 
      hledger  is  a  robust,  user-friendly,  cross-platform set of programs for
@@ -470,4 +470,4 @@
 SEE ALSO
      hledger(1), hledger-ui(1), hledger-web(1), ledger(1)
 
-hledger-web-1.52.3                 August 2026                    HLEDGER-WEB(1)
+hledger-web-1.52.4                 August 2026                    HLEDGER-WEB(1)
