diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for yesod-vite
+
+## 0.1.0.0 -- 2026-03-30
+
+* First version. Not ready enough to be a 1.0, but its full featured.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2026, Ian Kollipara
+
+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.
+
+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/Yesod/Vite.hs b/src/Yesod/Vite.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Vite.hs
@@ -0,0 +1,257 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TypeFamilies #-}
+
+-- |
+-- Module      : Yesod.Vite
+-- Description : Vite Integration for the Yesod Web Framework
+-- Copyright   : (c) Ian Kollipara, 2026
+-- License     : BSD-2-Clause
+-- Maintainer  : ian.kollipara@gmail.com
+-- Stability   : experimental
+-- Portability : POSIX
+--
+-- This module provides an integration to @[vitejs](https://vite.dev)@, a modern frontend build tool,
+-- to the "Yesod" web framework. This integration would allow the use of @vite@, setup in backend mode,
+-- to work seamlessly with "Yesod".
+--
+-- = Quick Start
+-- The assumption here is that your setup is similar to the default Yesod scaffold.
+-- You will need to have installed both @yesod-static@ and @yesod-vite@ for this to work.
+-- In addition, you will need the static subsite configured, as the route constructor is required.
+-- - Run @npm init -y@ or the equivalent for your build tool.
+--
+-- - Run @npm i -D vite@ or the equivalent for your build tool.
+--
+-- - Create a @vite.config.js@ to include the following:
+--
+-- @
+-- import { defineConfig } from "vite";
+-- import * as path from "node:path";
+--
+-- export default defineConfig({
+--   plugins: [],
+--   base: "/static/",
+--   build: {
+--     outDir: path.resolve("./static"),
+--     manifest: "manifest.json",
+--     rollupOptions: {
+--       input: path.resolve("./assets/src/app.js"),
+--     },
+--   },
+-- });
+-- @
+--
+-- - Install and configure "Yesod.Static" for your application.
+--
+-- - Setup 'YesodVite' as the following:
+--
+-- @
+-- instance YesodVite App where
+--   viteBuildDir :: app -> IO FilePath
+--   vitebuildDir app = return $ appStaticDir app
+--
+--   viteInDev :: app -> IO Bool
+--   viteinDev app = return . const True
+--
+--   viteRoute :: Route Static -> Route site
+--   viteRoute = StaticR
+-- @
+--
+-- - Add the @viteAsset \<asset-name\>@ to your default layout.
+module Yesod.Vite
+  ( YesodVite (..),
+    decodeManifest,
+    gatherAllCSS,
+    gatherAllModules,
+  )
+where
+
+import Data.Aeson
+  ( FromJSON (parseJSON),
+    eitherDecode,
+    withObject,
+    (.!=),
+    (.:),
+    (.:?),
+  )
+import qualified Data.ByteString.Lazy.Char8 as BS
+import Data.Functor (($>))
+import Data.List (nub)
+import qualified Data.Map as M
+import qualified Data.Text as T
+import GHC.Generics (Generic)
+import Yesod.Core
+  ( MonadIO (liftIO),
+    RenderRoute (Route),
+    WidgetFor,
+    Yesod,
+    getYesod,
+    whamlet,
+  )
+import Yesod.Static (Route (StaticRoute), Static)
+
+-- * Types
+
+-- | A manifest chunk as described by the vite configuration.
+data ViteManifestChunk
+  = ViteManifestChunk
+  { src :: Maybe FilePath,
+    file :: FilePath,
+    css :: [FilePath],
+    assets :: [FilePath],
+    isEntry :: Bool,
+    name :: Maybe T.Text,
+    isDynamicEntry :: Bool,
+    imports :: [T.Text],
+    dynamicImports :: [T.Text]
+  }
+  deriving (Show, Generic)
+
+instance FromJSON ViteManifestChunk where
+  parseJSON = withObject "ViteManifestChunk" $ \o ->
+    ViteManifestChunk
+      <$> o .:? "src"
+      <*> o .: "file"
+      <*> o .:? "css" .!= mempty
+      <*> o .:? "assets" .!= mempty
+      <*> o .:? "isEntry" .!= False
+      <*> o .:? "name"
+      <*> o .:? "isDynamicEntry" .!= False
+      <*> o .:? "imports" .!= mempty
+      <*> o .:? "dynamicImports" .!= mempty
+
+-- | A wrapper around a map to use for parsing the manifest file as described by vite.
+type ViteManifest = M.Map T.Text ViteManifestChunk
+
+-- | Attempt to decode a manifest
+decodeManifest :: BS.ByteString -> Either String ViteManifest
+decodeManifest = eitherDecode
+
+-- | Recursively traverse the manifest, starting at the @assetName@ to find all unique css values.
+gatherAllCSS ::
+  -- | The entry in the manifest.json. This is specified by your @vite.config.js@ as your input
+  T.Text ->
+  -- | The actual manifest to traverse.
+  ViteManifest ->
+  -- | The paths to all the css.
+  [FilePath]
+gatherAllCSS
+  entry
+  man =
+    let gatherAllCSS' :: T.Text -> [FilePath]
+        gatherAllCSS' k' =
+          case man M.!? k' of
+            Nothing -> []
+            Just e -> css e <> concat [gatherAllCSS' k | k <- imports e]
+     in nub $ gatherAllCSS' entry
+
+-- | Recursively traverse the manifest, starting at the @assetName@ to find all unique js modules.
+gatherAllModules ::
+  -- | The entry in the manifest.json. This is specified by your @vite.config.js@ as your input
+  T.Text ->
+  -- | The actual manifest to traverse.
+  ViteManifest ->
+  -- | The paths to all the js modules.
+  [FilePath]
+gatherAllModules entry man =
+  let gatherAllModules' :: T.Text -> [FilePath]
+      gatherAllModules' k' =
+        case man M.!? k' of
+          Nothing -> []
+          Just e -> file e : concat [gatherAllModules' k | k <- imports e]
+   in nub $ gatherAllModules' entry
+
+-- | = YesodVite
+-- This typeclass is used for configuring the uses of @vite@ in your "Yesod" application.
+class (Yesod site) => YesodVite site where
+  -- | The directory that vite builds to. This should match the output field in your @vite.config.js@.
+  viteBuildDir :: site -> IO FilePath
+
+  -- | Whether or not the site is in development. This is used to either serve production or development assets.
+  viteInDev :: site -> IO Bool
+
+  -- | How to create a static route. Typically you can set this to just `StaticR`.
+  viteRoute :: Route Static -> Route site
+
+  {-# MINIMAL viteBuildDir, viteInDev, viteRoute #-}
+
+  -- | The url to serve development resources from.
+  -- Defaults to @\http://127.0.0.1:5173\@
+  viteDevUrl :: site -> IO String
+  viteDevUrl = return . const "http://127.0.0.1:5173"
+
+  -- | The path to the built manifest. Defaults to `buildDir` + @/.vite/manifest.json@
+  viteManifest :: site -> IO FilePath
+  viteManifest site_ = do
+    basePath <- viteBuildDir site_
+    return $ basePath <> "/.vite/manifest.json"
+
+  -- | A helper used by @vite@ for @react@-based sites.
+  -- This is entirely optional, unless you are using a @react@-based frontend site.
+  -- This will be empty in production, but will enable hot reload in development.
+  viteEnableReactRefresh :: WidgetFor site ()
+  viteEnableReactRefresh = do
+    site_ <- getYesod
+    devUrl <- liftIO $ viteDevUrl site_
+    inDev <- liftIO $ viteInDev site_
+    if inDev
+      then
+        [whamlet|
+        <script type="module">
+          import RefreshRuntime from '#{devUrl}/@react-refresh';
+          RefreshRuntime.injectIntoGlobalHook(window);
+          window.$RefreshReg$ = () => {};
+          window.$RefreshSig$ = () => (type) => type;
+          window.__vite_plugin_react_preamble_installed__ = true;
+      |]
+      else
+        return mempty
+
+  -- | Insert a @vite@ asset into your site.
+  -- In development this will point at the vite development server.
+  -- In production, this will traverse the manifest and insert the correct tags
+  -- to your static assets.
+  viteAsset :: T.Text -> WidgetFor site ()
+  viteAsset assetName = do
+    site_ <- getYesod
+    inDev <- liftIO $ viteInDev site_
+    if inDev
+      then viteDevAsset assetName site_
+      else viteProductionAsset assetName site_
+
+-- = Internal
+
+-- | Handle the development asset
+viteDevAsset :: (YesodVite site, Yesod site) => T.Text -> site -> WidgetFor site ()
+viteDevAsset assetName s = do
+  devUrl <- liftIO $ viteDevUrl s
+  [whamlet|
+          <script type="module" src="#{devUrl}/@vite/client">
+          <script type="module" src="#{devUrl}/#{assetName}">
+  |]
+
+-- | Handle the production asset
+viteProductionAsset :: (YesodVite site, Yesod site) => T.Text -> site -> WidgetFor site ()
+viteProductionAsset assetName s = do
+  manifestPath <- liftIO $ viteManifest s
+  manifestContents <- liftIO $ BS.readFile manifestPath
+  let manifestE = decodeManifest manifestContents
+  case manifestE of
+    Left err -> error err $> mempty
+    Right manifest -> do
+      let isJS = T.isSuffixOf ".js" assetName
+          allCss = T.splitOn "/" . T.pack <$> gatherAllCSS assetName manifest
+          allModules = T.splitOn "/" . T.pack <$> gatherAllModules assetName manifest
+          assetUrl = T.splitOn "/" assetName
+      [whamlet|
+            $if isJS
+              <script type="module" src="@{viteRoute $ StaticRoute assetUrl []}">
+            $else
+              <link ref="stylesheet" href="@{viteRoute $ StaticRoute assetUrl []}">
+            $forall css <- allCss
+              <link ref="stylesheet" href="@{viteRoute $ StaticRoute css []}">
+            $forall module_ <- allModules
+              <link ref="modulepreload" href="@{viteRoute $ StaticRoute module_ []}">
+          |]
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,128 @@
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Main (main) where
+
+import qualified Data.ByteString.Lazy as BS
+import Data.Either (either, isRight)
+import Debug.Trace
+import Test.Hspec
+import TestApp
+import Yesod.Core
+import Yesod.Static
+import Yesod.Test
+import Yesod.Vite
+
+mkYesod
+  "App"
+  [parseRoutes|
+  /static StaticR Static appStatic
+  /react RrR GET
+  / HomeR GET
+|]
+
+getRrR :: Handler Html
+getRrR = defaultLayout viteEnableReactRefresh
+
+getHomeR :: Handler Html
+getHomeR = defaultLayout $ do
+  [whamlet|
+      ^{viteAsset "views/foo.js"}
+      <main>Hello World
+    |]
+
+instance Yesod App
+
+instance YesodVite App where
+  viteBuildDir :: App -> IO FilePath
+  viteBuildDir _ = return "static"
+
+  viteRoute = StaticR
+
+  viteInDev :: App -> IO Bool
+  viteInDev = return . appInDev
+
+  viteManifest :: App -> IO FilePath
+  viteManifest s =
+    if appInDev s
+      then do
+        basePath <- viteBuildDir s
+        return $ basePath <> "/.vite/manifest.json"
+      else
+        return "test/exampleManifest.json"
+
+manifestSpec :: Spec
+manifestSpec = do
+  describe "Manifest Parsing" $ do
+    it "should parse the manifest" $ do
+      manifest <- liftIO $ BS.readFile "test/exampleManifest.json"
+      decodeManifest manifest `shouldSatisfy` isRight
+
+    it "should extract all the CSS" $ do
+      manifest <- liftIO $ BS.readFile "test/exampleManifest.json"
+      let em = decodeManifest manifest
+      either
+        fail
+        ( \m ->
+            let css_ = gatherAllCSS "views/foo.js" m
+             in css_ `shouldNotSatisfy` null
+        )
+        em
+
+    it "should gather all the modules" $ do
+      manifest <- liftIO $ BS.readFile "test/exampleManifest.json"
+      let em = decodeManifest manifest
+      either
+        fail
+        ( \m ->
+            let mods_ = gatherAllModules "views/foo.js" m
+             in mods_ `shouldNotSatisfy` null
+        )
+        em
+
+yesodViteSpec :: Spec
+yesodViteSpec = do
+  describe "YesodVite" $ do
+    it "should return the expected path" $ do
+      app <- liftIO $ getApp True
+      manifest <- liftIO $ viteManifest app
+      manifest `shouldBe` ("static/.vite/manifest.json" :: FilePath)
+
+yesodViteWithYesodSpecDev :: Spec
+yesodViteWithYesodSpecDev =
+  yesodSpecWithSiteGenerator (getApp True) $ do
+    ydescribe "YesodVite (Widgets) [DEV]" $ do
+      yit "should get a correct response for RrR" $ do
+        get RrR
+        statusIs 200
+        bodyContains "@react-refresh"
+
+      yit "should have development vite urls" $ do
+        get HomeR
+        statusIs 200
+        bodyContains "@vite"
+
+yesodViteWithYesodSpecProd :: Spec
+yesodViteWithYesodSpecProd =
+  yesodSpecWithSiteGenerator (getApp False) $ do
+    ydescribe "YesodVite (Widgets) [PROD]" $ do
+      yit "should contain an empty response" $ do
+        get RrR
+        statusIs 200
+        bodyNotContains "@react-refresh"
+      yit "should return the full vite assets" $ do
+        get HomeR
+        statusIs 200
+        bodyNotContains "@vite"
+
+main :: IO ()
+main =
+  hspec $
+    manifestSpec
+      *> yesodViteSpec
+      *> yesodViteWithYesodSpecDev
+      *> yesodViteWithYesodSpecProd
diff --git a/test/TestApp.hs b/test/TestApp.hs
new file mode 100644
--- /dev/null
+++ b/test/TestApp.hs
@@ -0,0 +1,13 @@
+module TestApp where
+import Yesod.Static (Static, static)
+
+
+data App = App {
+  appStatic :: Static,
+  appInDev :: Bool
+}
+
+getApp :: Bool -> IO App
+getApp indev = do
+  s <- static "./"
+  return $ App s indev
diff --git a/yesod-vite.cabal b/yesod-vite.cabal
new file mode 100644
--- /dev/null
+++ b/yesod-vite.cabal
@@ -0,0 +1,130 @@
+cabal-version:      3.0
+-- The cabal-version field refers to the version of the .cabal specification,
+-- and can be different from the cabal-install (the tool) version and the
+-- Cabal (the library) version you are using. As such, the Cabal (the library)
+-- version used must be equal or greater than the version stated in this field.
+-- Starting from the specification version 2.2, the cabal-version field must be
+-- the first thing in the cabal file.
+
+-- Initial package description 'yesod-vite' generated by
+-- 'cabal init'. For further documentation, see:
+--   http://haskell.org/cabal/users-guide/
+--
+-- The name of the package.
+name:               yesod-vite
+
+-- The package version.
+-- See the Haskell package versioning policy (PVP) for standards
+-- guiding when and how versions should be incremented.
+-- https://pvp.haskell.org
+-- PVP summary:     +-+------- breaking API changes
+--                  | | +----- non-breaking API additions
+--                  | | | +--- code changes with no API change
+version:            0.1.0.0
+
+-- A short (one-line) description of the package.
+synopsis:           An integration of vitejs with Yesod
+
+-- A longer description of the package.
+description: An integration of vite and yesod. Provides a typeclass YesodVite which enables the viteAsset tag to function.
+
+-- URL for the project homepage or repository.
+homepage:           https://github.com/ikollipara/yesod-vite
+
+-- The license under which the package is released.
+license:            BSD-2-Clause
+
+-- The file containing the license text.
+license-file:       LICENSE
+
+-- The package author(s).
+author:             Ian Kollipara
+
+-- An email address to which users can send suggestions, bug reports, and patches.
+maintainer:         ian.kollipara@gmail.com
+
+-- A copyright notice.
+copyright:          Ian Kollipara, 2026
+category:           Web, Yesod
+build-type:         Simple
+
+-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
+extra-doc-files:    CHANGELOG.md
+
+-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
+-- extra-source-files:
+
+common warnings
+    ghc-options: -Wall
+
+source-repository head
+    type: git
+    location: https://github.com/ikollipara/yesod-vite
+    
+
+library
+    -- Import common warning flags.
+    import:           warnings
+
+    -- Modules exported by the library.
+    exposed-modules:
+        Yesod.Vite
+
+    -- Modules included in this library but not exported.
+    -- other-modules:
+
+    -- LANGUAGE extensions used by modules in this package.
+    -- other-extensions:
+
+    -- Other library packages from which modules are imported.
+    build-depends:
+        base >=4.18.3.0 && <4.19,
+        yesod-core >=1.6.27.0 && <1.7,
+        text >=2.0.2 && <2.1,
+        yesod-static >=1.6.1.0 && <1.7,
+        aeson >=2.1.2.1 && <2.2,
+        containers >=0.6.7 && <0.7,
+        bytestring >=0.11.5.4 && <0.12
+
+ 
+    -- Directories containing source files.
+    hs-source-dirs:   src
+
+    -- Base language which the package is written in.
+    default-language: Haskell2010
+
+test-suite yesod-vite-test
+    -- Import common warning flags.
+    import:           warnings
+
+    -- Base language which the package is written in.
+    default-language: Haskell2010
+
+    -- Modules included in this executable, other than Main.
+    other-modules:
+        TestApp
+
+    -- LANGUAGE extensions used by modules in this package.
+    -- other-extensions:
+
+    -- The interface type and version of the test suite.
+    type:             exitcode-stdio-1.0
+
+    -- Directories containing source files.
+    hs-source-dirs:   test
+
+    -- The entrypoint to the test suite.
+    main-is:          Main.hs
+
+    -- Test dependencies.
+    build-depends:
+        base >=4.18.3.0 && <4.19,
+        yesod-vite,
+        hspec >=2.11.12 && <2.12,
+        HUnit >=1.6.2.0 && <1.7,
+        text >=2.0.2 && <2.1,
+        yesod-test >=1.6.16 && <1.7,
+        yesod-static >=1.6.1.0 && <1.7,
+        yesod-core >=1.6.27.0 && <1.7,
+        bytestring >=0.11.5.4 && <0.12,
+        blaze-html >=0.9.2.0 && <0.10
