diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## 0.1.0
+
+- Initial release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2022, Tristan de Cacqueray
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/XStatic.hs b/src/XStatic.hs
new file mode 100644
--- /dev/null
+++ b/src/XStatic.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module XStatic (
+    -- * xstatic api
+    XStaticFile (..),
+    xstaticApp,
+
+    -- * file-embed re-export
+    embedFile,
+) where
+
+import Data.Binary.Builder (Builder, fromByteString)
+import Data.ByteString (ByteString)
+import Data.ByteString qualified as BS
+import Data.ByteString.Char8 (breakEnd, pack)
+import Data.ByteString.Char8 qualified as BS8
+import Data.FileEmbed (embedFile)
+import Data.Map.Strict qualified as Map
+import Data.Version (Version, showVersion)
+import Network.HTTP.Types.Header (ResponseHeaders)
+import Network.HTTP.Types.Status qualified as HTTP
+import Network.Wai qualified
+
+-- | A static file definition
+data XStaticFile = XStaticFile
+    { name :: ByteString
+    -- ^ The expected request filename. The name must not have any slash '/'.
+    , content :: ByteString
+    -- ^ The file content gzip compressed.
+    , contentVersion :: Version
+    -- ^ The file version for the etag header.
+    , contentType :: ByteString
+    -- ^ The content type, e.g. `text/javascript` or `text/css`.
+    }
+
+{- | Create a wai application to serve 'XStaticFile'.
+
+ The request are served whenever the basename match, ignoring the parent directory or the query string.
+-}
+xstaticApp :: [XStaticFile] -> Network.Wai.Application
+xstaticApp xs = \req resp ->
+    let (_, basename) = breakEnd (== '/') (Network.Wai.rawPathInfo req)
+     in resp $ case Map.lookup basename files of
+            Just (builder, headers) ->
+                let body = case Network.Wai.requestMethod req of
+                        "HEAD" -> mempty
+                        _ -> builder
+                 in Network.Wai.responseBuilder HTTP.status200 headers body
+            Nothing -> Network.Wai.responseLBS HTTP.status404 mempty mempty
+  where
+    files :: Map.Map ByteString (Builder, ResponseHeaders)
+    files = Map.fromList $ map toItem xs
+
+    toItem :: XStaticFile -> (ByteString, (Builder, ResponseHeaders))
+    toItem xf =
+        ( name xf
+        ,
+            ( fromByteString $ content xf
+            , (addGzipHeader $ content xf)
+                [ ("cache-control", "public, max-age=604800")
+                , ("content-length", pack . show . BS.length $ content xf)
+                , ("content-type", contentType xf)
+                , ("connection", "keep-alive")
+                , ("etag", versionToEtag $ contentVersion xf)
+                , ("keep-alive", "timeout=5, max=100")
+                ]
+            )
+        )
+
+addGzipHeader :: ByteString -> ResponseHeaders -> ResponseHeaders
+addGzipHeader fileContent
+    | isGzip fileContent = (("content-encoding", "gzip") :)
+    | otherwise = id
+
+isGzip :: ByteString -> Bool
+isGzip = BS.isPrefixOf "\x1f\x8b\x08" -- the gzip magic number for deflate
+
+versionToEtag :: Version -> ByteString
+versionToEtag = BS8.unwords . BS8.split '.' . pack . showVersion
diff --git a/xstatic.cabal b/xstatic.cabal
new file mode 100644
--- /dev/null
+++ b/xstatic.cabal
@@ -0,0 +1,42 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.35.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           xstatic
+version:        0.1.0
+synopsis:       Low-Fat static file packaging for Haskell projects
+description:    Low-Fat static file packaging for Haskell projects.
+category:       JavaScript
+homepage:       https://github.com/TristanCacqueray/haskell-xstatic#readme
+bug-reports:    https://github.com/TristanCacqueray/haskell-xstatic/issues
+author:         Tristan Cacqueray
+maintainer:     tdecacqu@redhat.com
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/TristanCacqueray/haskell-xstatic
+
+library
+  exposed-modules:
+      XStatic
+  other-modules:
+      Paths_xstatic
+  hs-source-dirs:
+      src/
+  ghc-options: -Weverything -Wno-implicit-prelude -Wno-missing-kind-signatures -Wno-missing-safe-haskell-mode -Wno-unsafe
+  build-depends:
+      base <5
+    , binary
+    , bytestring
+    , containers
+    , file-embed
+    , http-types
+    , wai
+  default-language: Haskell2010
