Python Static Site Generator for Serverless Applications π
Kaktos β κάκΟΞΏΟ (Greek for βcactusβ)
Kaktos is a powerful Python Static Site Generator designed to create highly efficient serverless applications. Why pay for hosting when you can deploy a completely static site for free?
Create beautiful static websites, e-commerce stores, blogs, landing pages, and sales pages with advanced pagination and dynamic features, all without the hassle of server-side dependencies! π»
β¨ Features
- Static Website Generator β Create responsive, fast, and secure static sites πΌοΈ
- Static E-commerce β Build fully functioning static shopping websites π
- Static Blog β Advanced blog with pagination and dynamic content π
- Landing Pages & Sales Pages β Perfect for creating high-conversion pages for any purpose ποΈ
- Easy to Use β Intuitive design that requires no server-side programming π‘
- Serverless β Deploy to platforms like Netlify, Cloudflare, or Render with no need for server management βοΈ
- Many Free Hosting Options β Many companies offer free hosting for static sites, as no server-side processing is required π
- Fast & Secure β Static sites are inherently faster and more secure β‘
- No Hosting Fees β Fully serverless deployment means no hosting costs π
- SEO Optimized β Generate clean, SEO-friendly pages π΅οΈββοΈ
- Customizable Templates β Based on the powerful Jinja2 templating engine π¨
- Integrated frontend β All CSS, JavaScript, and images live under
frontend/, the Python build runs Vite and npm for you, and the site UI is styled with Tailwind CSS βοΈ
π€ Why Kaktos?
With Kaktos, you can deploy your website without worrying about server management, database configuration, or paying for hosting. Focus on your content, and let Kaktos handle the rest. Enjoy the benefits of a serverless architecture, which has become a major trend in web development, reducing operational costs and simplifying the deployment process for companies of all sizes.
- No need to manage infrastructure
- No ongoing hosting fees
- Scalable and fast deployment
- Ideal for websites, blogs, e-commerce, and landing pages
π All-in-One Solution
Unlike most static site generators, which focus on specific tasks, Kaktos provides an all-in-one solution.
Whether you need to build a blog, an e-commerce store, a landing page, or any other static website, Kaktos brings it all together in one platform, simplifying your workflow and allowing you to manage everything in a single place.
π¬ Demo
Cloudflare:
Netlify:
Amplify:
https://main.d27ze19drzixy0.amplifyapp.com
Render:
Requirements
- Python 3.9+
- Node.js 20.19+ or 22.12+ (LTS 22 recommended) and npm on your
PATHso the Python build can invoke Vite 8 (you never runnpmyourself for normal development or production builds). The repo pins 22 via.nvmrc,netlify.toml,render.yaml, andamplify.ymlwhere the host reads them.
π How to Get Started
Installation
Option 1: Using Virtual Environment (Recommended)
Using a virtual environment is the recommended approach as it keeps your project dependencies isolated from your system Python installation.
- Create a virtual environment:
python3 -m venv venv
-
Activate the virtual environment:
-
On macOS/Linux:
source venv/bin/activate -
On Windows:
venv\Scripts\activate
-
-
Install dependencies:
pip install -r requirements.txt
- Deactivate the virtual environment (when you're done working):
deactivate
Option 2: Direct Installation
If you prefer to install dependencies directly to your system Python:
python3 -m pip install -r requirements.txt
Note: It's recommended to use a virtual environment (Option 1) to avoid conflicts with other Python projects.
π» Development
To work in development mode, you only need execute one command:
python3 kaktos.py
With python3 kaktos.py you get one integrated dev loop: LiveReload watches the paths below, runs build_pages() on each save (Vite for CSS/JS in frontend/src/, then Frozen-Flask for HTML from templates/, then copies frontend/dist/ to build/static/ and frontend/raw/ into build/), and then asks the browser to refresh when the tab is connected.
What triggers a rebuild:
- HTML (Jinja pages and partials) β anything under
templates/ - CSS and JS processed by Vite (including Tailwind) β under
frontend/src/(for examplemain.css,main.js) - Static files merged into the Vite output β under
frontend/public/(images, etc.; published under/static/β¦) - Files at the site root after build β under
frontend/raw/(favicon,robots.txt, β¦) - Tooling β
frontend/package.json,frontend/package-lock.jsonif present, and root configs infrontend/matchingvite.config.*,tailwind.config.*,postcss.config.*, oreslint.config.*
Each cycle completes the Vite step before the static pages are regenerated so build/ stays consistent. The browser may skip a rapid second reload within a few seconds (LiveReload throttling)βsee Troubleshooting if needed.
This command always forces development mode, with or without environment variable.
You only run python3 kaktos.py for local development and python3 kaktos.py build for production output. Dependencies under frontend/ are installed automatically on first build when needed.
π Production
To generate production files, you only need execute one command:
python3 kaktos.py build
All files will be generated in build folder.
If you set environment variable KAKTOS_DEBUG=True, kaktos will build all files for development mode, example:
KAKTOS_DEBUG=True python3 kaktos.py build
If you want start a web server to test files inside build folder use:
python3 kaktos.py serve
π¦ Deploying with One Click
Netlify:
Render:
ποΈ Project Structure
kaktos.pyβ main entrypoint that dispatches commandsrequirements.txtβ Python dependenciestemplates/layoutsβ base layouts pages extendtemplates/pagesβ page templates (frozen routes)templates/sharedβ partials, macros, shared fragmentsmodules/β Python code (config.py, routes,frontend.py, β¦)frontend/β all CSS, JS, and images for the site:frontend/src/β Vite entry (main.js, Tailwindmain.css)frontend/public/β static files copied into/static/β¦(logos, gallery images, etc.)frontend/raw/β files copied to the build root (favicon,robots.txt,CNAME,site.webmanifest, β¦); nothing here runs through Viteβthey are copied as-is to the published site root, unlikefrontend/public/, which is emitted under/static/β¦.frontend/package.json/vite.config.jsβ Node toolchain (invoked only from Python)
extras/config/β YAML sample data (blog posts, products, categories)
π¨ Frontend assets in templates
Static assets are split by folder: anything under frontend/public/ is published under /static/β¦ (on disk: build/static/). Anything under frontend/raw/ is copied to the root of the generated site (same path as in that folder, e.g. /robots.txt). In Jinja you reference them with kaktos.frontend so you never hard-code hashed filenames or the /static URL prefix.
kaktos.frontend API
| Call | Resolves |
|---|---|
{{ kaktos.frontend.url('images/logo.png') }} |
frontend/public/images/logo.png β /static/images/logo.png |
{{ kaktos.frontend.root_url('favicon.ico') }} |
frontend/raw/favicon.ico β /favicon.ico |
{{ kaktos.frontend.abs_url('images/logo-og.png') }} |
Same as url() but absolute for Open Graph / sharing, using config.base_url (pass through unchanged if the value already starts with http:// or https://) |
{{ kaktos.frontend.styles_all() }} |
Injects <link> tags for every Rollup input entryβs CSS (sorted, deduped) |
{{ kaktos.frontend.scripts_all() }} |
Injects <script type="module" β¦ defer> for every Rollup input entryβs JS (sorted) |
{{ kaktos.frontend.styles_for('main') }} |
Same as styles_all() but only for the named entry (e.g. split layouts) |
{{ kaktos.frontend.script_for('main') }} |
Same as scripts_all() but only for the named entry |
The default layout uses styles_all() in templates/shared/head.html and scripts_all() in templates/shared/footer.html so new Vite inputs in frontend/vite.config.js are picked up automatically. Use styles_for('β¦') / script_for('β¦') when you need explicit control (per-page bundles, order, or omitting entries).
Gallery folders on disk: set gallery_dir in modules/config.py (absolute path). The gallery template uses file.find_dirs(kaktos.config.gallery_dir, "*") so you can point it anywhere without changing core code. Thumbnails use GLightbox (glightbox on the link + data-gallery per album); adjust options in frontend/src/main.js if needed.
Layout helpers page_container_start / page_container_end live in templates/shared/macros.html (optional wrappers around the main content column).
Examples (inline)
<img src="{{ kaktos.frontend.url('images/photo.jpg') }}" alt="Photo">
<a href="{{ kaktos.frontend.root_url('ads.txt') }}">Ads</a>
<meta property="og:image" content="{{ kaktos.frontend.abs_url('images/logo-og.png') }}">
YAML and data files
Use paths relative to frontend/public/ in YAML (e.g. images/blog/post.jpg), then in templates wrap dynamic fields with kaktos.frontend.url(...) when outputting src or href (unless the field is already a full https:// URL).
π§ Commands
Each command supported by Kaktos is a Python file located in the modules/commands/ folder.
To add new commands, simply create a new Python file in the modules/commands/ folder and implement the def run(params={}) method within it.
π Templates
All templates (html files) are based on Jinja2 library. You can see it here:
https://jinja.palletsprojects.com/en/stable/
π οΈ Troubleshooting
β’ Python version
CI and local builds use the Python you configure. The repo includes .python-version (e.g. 3.13) for tools that respect it.
Hosting platforms ship their own Python and Node images. See their current defaults and how to pin a version:
- Netlify β Available software at build time
- Cloudflare Pages β Build image / language support
- Render β Environment and native runtimes
β’ Node / npm not found or βVite requires Node.js β¦β
Install Node.js 22 LTS (or 20.19+). On hosts that ignore .nvmrc, set NODE_VERSION=22 (or equivalent) in the service environment. You still only run python3 kaktos.py or python3 kaktos.py build; the first run installs frontend/node_modules when needed.
β’ LiveReload did not refresh the browser
Keep the tab open so the LiveReload WebSocket stays connected (the static build/ output still updates on disk even if the browser skips a reload). The bundled livereload client also ignores extra reload signals if they arrive within a few seconds of the last oneβif you save many files in quick succession, do one manual refresh once the terminal shows building done.
β’ Template changed, but not reloaded
Invalid Jinja2 syntax can prevent your HTML template from being built.
Check your terminal to see the error message, the HTML file and the line number where invalid syntax was detected.
β Buy Me a Coffee
πΌοΈ Images
Place images under frontend/public/images/ (see sample layout: images/logo.png, images/gallery/β¦, images/blog/β¦). Demo stock photos were sourced from Unsplash.
π License
Copyright (c) 2021-2026, Paulo Coutinho