Skip to content
Dashboard

FastHTML for full web apps in pure Python

FastHTML builds server-rendered web applications in Python. Python function calls produce HTML, and HTMX updates parts of a page when a user submits a form or clicks a button. You can build common interactions without writing JavaScript or adding a frontend build step.

FastHTML fits interfaces that can wait for the server to respond, including forms, internal tools, and dashboards. Interfaces that need immediate local updates or offline behavior require browser-side code. When deploying, choose where to store data and session configuration so different function instances can use them.

Copy link to headingWhat is FastHTML?

FastHTML represents HTML elements as Python function calls. A route handler can query a database and return a component that the framework renders as HTML. HTMX sends requests from the browser and inserts the returned HTML into the existing page.

The FastHTML class extends Starlette and implements the Asynchronous Server Gateway Interface (ASGI). Locally, Uvicorn can serve the application. A host with ASGI support can load the same application object.

Jeremy Howard introduced FastHTML through Answer.AI in August 2024. Install the package as python-fasthtml and import it as fasthtml. The project is below version 1.0 and has introduced breaking changes, so test upgrades against the version your application uses.

Copy link to headingHow FastHTML differs from template-based frameworks

Jinja and Django templates usually place markup in template files. FastHTML builds it with Python callables, so a reusable part of a page can be a function that returns other components. Markup, routing, and data access all use Python. Templates suit teams that prefer to keep markup in a separate presentation language.

Both templates and FastHTML can return full pages or HTML fragments. In Next.js, Server Components run on the server, while Client Components provide browser state and event handlers. On an initial page load, Next.js generates HTML and hydrates the Client Components. FastHTML keeps rendering on the server but answers HTMX requests with small page fragments, and that choice carries through to how each approach handles markup, reuse, and deployment:

Axis

FastHTML

Jinja/Django templates

React/Next.js

Language for UI

Python

A template language, usually in HTML files

JavaScript or TypeScript, usually with JSX or TSX

JavaScript authored by the developer

Optional for common HTMX interactions

Depends on the interaction layer, which can include HTMX

Used for browser-side behavior

Component reuse

Python callables

Includes macros and template inheritance

React components

Deployment unit

An ASGI application

A Python web application

Depends on the framework; Next.js supports a server deployment or static export

HTMX works with any backend that returns HTML. A Django or Flask application can adopt fragment updates without moving to FastHTML.

Copy link to headingWhat hypermedia-first gives Python developers

In a hypermedia application, the server returns HTML that describes the next interface state. HTMX inserts that HTML into the page. The browser still parses and renders the result, but the application does not need a separate JavaScript renderer to turn a JSON response into UI.

FastHTML also supports WebSockets and server-sent events (SSE). Its HTMX extension support includes chunked-transfer updates for streaming HTML. Those options let the server deliver updates after the initial page response.

Python teams can use this model to reduce frontend work:

  • No second language: Python can handle routing, data access, and HTML generation. Common interactions do not require a separate JavaScript application.

  • No API layer: Handlers can return HTML directly. An application does not need a JSON API solely to connect its own frontend to its backend.

  • No client-side store: Durable application state can stay in a database, with session information in a signed cookie. A separate client-side state store is optional.

  • No build step: The default application needs Python source and dependencies. There is no JavaScript bundler or transpiler to configure unless the application adds tooling that requires one.

A form submission can wait for a server response. A drawing tool that updates on every pointer movement needs more work in the browser. Check which interactions need an immediate response or must work offline.

Copy link to headingHow FastHTML works

A FastHTML application combines components for HTML, an application object, route handlers, and HTMX attributes. Components produce markup, route handlers respond to requests, and HTMX applies updates in the browser.

Copy link to headingBuild markup with FT components

An FT, short for FastTag, represents an HTML element with a tag name, children, and attributes. Functions such as Div, P, and Img construct those elements. Positional arguments supply children, and keyword arguments supply attributes.

Attribute names follow a few Python-specific rules. cls becomes class, a leading underscore is removed, and underscores in names such as hx_get become hyphens. Boolean attributes set to True appear without a value; those set to False are omitted.

Compose components by calling them from other Python functions. To inspect their output, render them with to_xml(). The component docs recommend Any for component return types, which can include FT objects, strings, tuples, and other supported values. Static checking, therefore, catches fewer errors in component output.

Copy link to headingCreate the application and configure sessions

Without a database configuration, fast_app() returns app and rt. The first is the ASGI application; the second registers routes. Database configurations can return additional table objects and types.

Default page headers include Pico CSS, HTMX, and helper scripts such as Surreal. Set pico=False to omit Pico CSS when the application supplies its own styling.

Sessions use a signed browser cookie. Signing protects against undetected changes; it does not encrypt the contents. If you omit the secret key, FastHTML reads a .sesskey file or creates one. It can create the file locally, but Vercel Functions cannot write to the project directory at runtime.

For deployment, supply a stable secret through an environment variable:

import os
from fasthtml.common import fast_app
app, rt = fast_app(secret_key=os.environ["SESSKEY"])

Set SESSKEY before importing the application. A missing variable then stops startup. All instances in the same deployment environment need a consistent key to read the same signed sessions.

Copy link to headingConnect routes to handlers

A route maps a URL and HTTP method to a Python handler. With @rt("/path"), a function named get or post handles that method. A bare @rt uses the function name as the path and normally accepts GET and POST; index maps to /.

Handler annotations tell FastHTML how to parse request values. It can read path parameters, query strings, and form data. Special parameters include sess for the session and htmx for parsed HTMX request headers.

When a handler returns an FT component, FastHTML normally wraps it in a full page for a direct browser request and returns a fragment for an HTMX request. A handler can also return a Starlette Response. Include HtmxResponseHeaders with response content to set headers such as HX-Location.

The decorated route's .to() method generates its URL. For example, hx_post=yoyo.to() targets the registered yoyo route without repeating its path in the component.

Copy link to headingHTMX attributes and script loading

HTMX attributes specify the request, its target, and the swap behavior. In Python, hx_get and hx_post send requests, hx_target selects the destination element, and hx_swap controls how to insert the response. The default swap replaces the target's contents. hx_swap_oob lets a response update other named regions too.

FastHTML 0.14.14 includes HTMX 2.0.7 by default. Setting htmx4=True selects HTMX 4.0.0. Check the application's extensions and behavior before switching versions.

The default scripts load from jsDelivr. If a network policy or Content Security Policy (CSP) prevents HTMX from loading, interactions that depend on it will fail. To host the scripts yourself, add local copies, disable the default headers with default_hdrs=False, and add Script(src=...) headers for the files the application needs. You must supply those replacements when you disable the defaults.

Copy link to headingStatic files and dependencies

fast_app() registers a static-file route that matches supported file extensions. It uses the current project directory by default. Set static_path="public" to serve assets from a dedicated directory instead. The route serves images, stylesheets, and scripts without a separate application. Keep only files intended for the browser in the static directory.

Declare dependencies in requirements.txt or pyproject.toml. Vercel's Python runtime supports both, including a pyproject.toml with an optional uv.lock.

The standard python-fasthtml installation includes Starlette and Uvicorn. Fastlite, MonsterUI, IPython, and lxml are development extras in the current package metadata. Declare optional libraries explicitly when application code uses them.

Vercel's standard Python bundle limit is 500 MB uncompressed, including application files and dependencies. Large Functions supports up to 5 GB in public beta with Fluid compute. Existing projects must opt in with VERCEL_SUPPORT_LARGE_FUNCTIONS=1. Secure Compute and Static IPs are currently unsupported.

Copy link to headingFastHTML development best practices

Start with a local route, then check fragment responses, form parsing, and dependency upgrades. Repeat those checks before deploying an upgrade.

Copy link to headingUse serve() for local development

FastHTML's serve() starts Uvicorn when the module runs directly. In the usual application pattern, it does not start a second server when a hosting runtime imports the module. You do not need an additional if __name__ == "__main__": guard around that call.

Save this local example as main.py:

main.py
from fasthtml.common import *
app, rt = fast_app()
@rt("/")
def get():
return Titled("FastHTML", P("Hello from Python."))
serve()

Run python main.py and open http://localhost:5001. The default port is 5001 unless the PORT environment variable or a serve() argument overrides it.

Copy link to headingReturn a fragment for a partial update

When one part of the page changes, return the markup for that part. This avoids sending a full document and preserves elements outside the swap target. Replacing the target can still discard form values or other states inside it.

This button requests a paragraph and inserts it into #dest:

from fasthtml.common import *
app, rt = fast_app()
@rt
def index():
return Titled(
"Hello",
Button("Click", hx_get="/change", hx_target="#dest"),
Div(id="dest"),
)
@rt("/change")
def get():
return P("The server returned this paragraph.")
serve()

HTMX sends a GET to /change and swaps the returned paragraph into the target. The browser runs HTMX, but the example needs no application JavaScript.

Copy link to headingParse form data into a typed handler parameter

Define the expected fields in a type and let FastHTML construct it from the form body. Parsing converts the input into Python values. Application rules, such as rejecting a blank name or checking permissions, still belong in the handler.

This complete local example posts to /rooms and prepends a list item:

from dataclasses import dataclass
from fasthtml.common import *
app, rt = fast_app()
@dataclass
class Room:
name: str = ""
def room_form():
return Form(
Input(id="name", name="name", placeholder="New room name", required=True),
Button("Create room"),
hx_post="/rooms",
hx_target="#rooms-list",
hx_swap="afterbegin",
)
@rt("/")
def get():
return Titled("Rooms", room_form(), Ul(id="rooms-list"))
@rt("/rooms")
def post(room: Room):
name = room.name.strip()
if not name:
return Li("Enter a room name.")
return Li(name)
serve()

FastHTML constructs Room from the submitted name field. This example renders the result without saving it. To keep a room after a reload or make it available to later requests, validate and save it to durable storage before returning its markup.

Copy link to headingPin and test framework upgrades

FastHTML has introduced breaking changes between minor versions. Version 0.9.0 renamed the route URL method from .rt to .to. Older examples that use .rt() need updating.

Pin a tested version, such as python-fasthtml==0.14.14, and check full-page routes, fragment routes, and form submissions when upgrading. The import namespace is lowercase: from fasthtml.common import *. FastHTML.common is not the package name.

Copy link to headingCheck the scripts loaded by the page

Pinning Python packages does not pin every browser dependency. FastHTML's default headers include four scripts from jsDelivr. HTMX and the FastHTML helper script use versioned URLs; Surreal and css-scope-inline use GitHub main branch URLs.

Files loaded from main can change independently of the Python package. Set fast_app(surreal=False) to omit both helpers when the application does not use them. If it does, use pinned or self-hosted copies to control which versions the browser loads.

Copy link to headingFastHTML limitations

Check how FastHTML handles static checking, data storage, browser behavior, and library integration. These choices determine how you test components, save data, and add browser interactions.

Copy link to headingType system and IDE support

An Any annotation gives a type checker little information about a component's return value. The 0.14.14 package also has no py.typed marker, so mypy does not treat it as a typed installed package by default.

This limits the ability to check dynamic component composition. It does not prevent type checking of application models and other code with concrete annotations. Test the rendered output and routes alongside static analysis.

Copy link to headingDatabase access on serverless functions

FastHTML examples often use Fastlite, a layer over SQLite. The db_file argument to fast_app() opens a database file and can create it when needed. With a persistent disk on a single server, that file can hold application data.

Vercel Function instances cannot share a local database file, and temporary files do not provide durable storage. Use an external database or hosted store from the Vercel Marketplace when requests need the same persistent data. FastHTML can use databases other than SQLite.

Copy link to headingComplex client-side UI patterns

An HTMX request waits for the server to respond. That works for many forms and page updates, but it cannot provide immediate local behavior while the network is unavailable. Dragging, drawing, optimistic updates, and offline work may need JavaScript and browser-held state.

Out-of-band swaps allow a single response to update multiple page regions. When those regions depend on each other, the server handler must coordinate their updates. FastHTML can serve browser scripts; its default model does not provide a client-side application state system.

Copy link to headingAI assistant support and project maturity

FastHTML's README recommends providing its large language model (LLM) context file to coding assistants. The file describes current framework conventions, including route URL generation with .to().

Check the generated code against the installed release and run it in the application's environment. The context file helps with API usage; running the code checks whether it works with your setup.

Copy link to headingComponent and database integrations

FastHTML can use JavaScript libraries and web components. React, Vue, and Svelte components are not native FT components, so they require a separate browser integration. For example, React can mount components within an existing page, but the application must load and manage that JavaScript.

MonsterUI provides Python UI components for FastHTML. For database access, applications can use SQLAlchemy directly or the FastSQL library. Check that the libraries your team needs support its data model and deployment requirements.

Copy link to headingFastHTML vs. FastAPI, Streamlit, and Next.js

Compare how these frameworks handle rendering, state, and requests. The response format alone does not determine which framework fits an application.

Axis

FastHTML

FastAPI

Streamlit

Next.js

Primary output

HTML pages and fragments

API responses, commonly JSON, with OpenAPI tooling

Widgets and charts in Streamlit's frontend

HTML and React output using Server and Client Components

JavaScript authored

Optional for common interactions

Depends on the frontend

Optional for standard widgets

JavaScript or TypeScript for application components

Where state lives

Application-defined; commonly a database and signed session cookie

Application-defined, including databases and session systems

Server-side Session State tied to a browser session

Server-side data and browser state in Client Components

Typical use

HTML applications, forms, and internal tools

APIs for frontend, mobile, and integration clients

Data exploration and dashboards

Applications that combine server rendering with browser-side interaction

Vercel deployment path

ASGI function and FastHTML Boilerplate

Native FastAPI framework support

No native Streamlit framework preset; assess its server and session lifecycle separately

Native Next.js support

A few JSON endpoints do not require replacing a FastHTML application. Consider FastAPI when the application depends on API validation and generated documentation. Streamlit suits data workflows that fit its widgets and script rerun model. Next.js combines React's browser-side interaction with server rendering.

Copy link to headingWhen to use FastAPI

FastAPI generates an OpenAPI schema, provides Swagger UI and ReDoc, and uses Pydantic for request validation. These tools help when other applications consume the API and need a documented contract.

FastHTML can return JSON, and FastAPI can return HTML. The distinction is which framework's defaults and tooling fit most of the application. Similar route syntax does not make FastHTML and FastAPI applications interchangeable.

Copy link to headingWhen to use Streamlit

Streamlit normally reruns a script after a widget interaction. The st.fragment decorator, generally available since 1.37.0, supports partial reruns. This model fits data exploration when a changed input should refresh the computed output.

Streamlit also provides multipage navigation, Session State, and OpenID Connect (OIDC) authentication. FastHTML gives developers more direct control over HTTP handlers, returned markup, and fragment targets. Choose based on how much of that control the application needs and how much UI its team can build with Streamlit's widgets.

Copy link to headingWhen to use Next.js

Next.js Client Components can hold state, handle events, run effects, and access browser APIs. React's useOptimistic supports optimistic UI. Server Components handle work that does not need those browser capabilities.

Offline behavior requires additional implementation, such as a service worker and IndexedDB storage. It does not follow automatically from using Next.js. Teams can also keep a Python backend and use Next.js for the frontend on Vercel.

Copy link to headingHow Vercel runs FastHTML in production

Vercel added zero-configuration FastHTML deployment on July 29, 2024. The Python runtime loads the ASGI application as a Vercel Function. Deploy through the Vercel CLI or a connected Git repository.

Check the entrypoint, durable storage, open connections, and concurrent requests when moving an application from a local server.

Copy link to headingZero-config deployment from main.py

FastHTML's usual setup is app, rt = fast_app() followed by serve() for local development. Vercel loads the top-level app object rather than starting Uvicorn through that call.

Supported entry files include app.py, index.py, server.py, main.py, wsgi.py, and asgi.py, including supported locations under src/ or app/. The FastHTML Boilerplate uses the main.py pattern with dependencies in requirements.txt.

For a custom module, set tool.vercel.entrypoint in pyproject.toml to a module:variable value, such as my_package.web:app. Supply the session key through the deployment environment before the runtime imports the app.

Copy link to headingHosted Redis from the Vercel Marketplace

Vercel Functions have a read-only filesystem with up to 500 MB of writable /tmp scratch space. That scratch space is temporary and does not provide shared storage across instances.

Connect a managed database or a Redis store for shared data. Redis integrations in the Vercel Marketplace add connection credentials to the project's environment variables.

Answer.AI's todos-vercel sample demonstrates external Redis storage. Its session setup contains a hardcoded fallback secret. Replace that fallback with a required SESSKEY variable in production.

Copy link to headingWebSockets on Vercel Functions

FastHTML supports WebSockets on Vercel Functions through Starlette and can load the HTMX WebSocket extension with exts=["ws"] in fast_app().

Connections close after reaching the configured function duration. The HTMX WebSocket extension reconnects automatically after certain unexpected closures. A reconnect may reach another instance. After a deployment, existing connections can remain on the previous deployment while new connections reach the new one. Use a preview deployment to test that transition with shared external state.

Copy link to headingFluid compute for concurrent fragment requests

Fluid compute lets multiple Python invocations share an instance. When a fragment handler waits on asynchronous database or API work, the instance can make progress on other requests. Sharing instances can reduce how many the application needs. Test concurrent requests and database connection usage to see how the handler and its dependencies behave under load.

Memory limits are 2 GB on Hobby and 4 GB on Pro and Enterprise. With Fluid compute, function duration defaults to 300 seconds on all plans. Hobby allows up to 300 seconds, while Pro and Enterprise allow 800 seconds and an extended 1,800-second beta.

The extended duration beta supports Python 3.12, 3.13, and 3.14. Configure durations above 800 seconds per function in vercel.json; Secure Compute and Static IPs currently do not support those longer durations. WebSocket connections and long-running handlers must fit the configured duration.

Copy link to headingShip your FastHTML app on Vercel

Start with a form or fragment route that represents the application's main interaction. Check its deployed response time, session behavior, and access to shared data. Use the results to decide whether server round-trips meet the interface's needs and whether sessions and data work across instances.

Vercel covers the infrastructure side with:

  • Fluid compute for Python ASGI: One instance handles concurrent HTMX fragment requests, so I/O-bound handlers don't each start a separate instance.

  • Zero-config FastHTML deployment: Push main.py and requirements.txt through Git, and Vercel detects the entrypoint and loads app with no configuration file.

  • Databases through the Vercel Marketplace: Connect to a managed database in place of SQLite and read its URL from an environment variable.

  • Preview deployments per pull request: Every pull request gets its own URL, so HTMX swaps and WebSocket behavior can be tested before merging.

  • Managed Python versions: Set the version in .python-version or pyproject.toml, and the runtime installs it without a custom container.

Start a new project from the FastHTML Boilerplate, or browse templates for other Python starting points.

Copy link to headingFrequently asked questions about FastHTML

Copy link to headingIs FastHTML production-ready?

Yes. It can be, when the application's interaction model and dependencies fit the deployment environment. Pin a tested release and check full-page responses, fragment swaps, and form validation. On Vercel, configure a stable session key, use external storage for durable data, and allow the scripts your pages need through the CSP.

Copy link to headingWhich Python version does FastHTML need on Vercel?

FastHTML 0.14.14 requires Python 3.10 or later. Vercel supports Python 3.12, the current default, as well as 3.13 and 3.14. All three satisfy that requirement. Use a supported version in .python-version to align the interpreter used locally and in deployment.

Copy link to headingCan I use SQLite with FastHTML on Vercel?

A local SQLite file cannot hold shared, durable application data across Vercel Function instances. Fluid compute can reuse an instance, but later requests may reach another one. Use a hosted database or another durable store, such as Redis, for persistent shared state.

Copy link to headingHow is FastHTML different from FastAPI?

They are separate frameworks with different request handling and application conventions. FastHTML emphasizes HTML generation and HTMX interactions. FastAPI emphasizes API validation and OpenAPI tooling. Both can return HTML or JSON, but code that uses one framework's decorators and parameter handling does not automatically run on the other.

Ready to deploy?