Project structure¶
Where do files go? Honestly: wherever you'd put them in any other Django project. reflex-django doesn't impose a layout on you. Below is the layout we recommend because it's the one most teams find easiest to work with.
The recommended layout¶
myproject/
├── manage.py
├── pyproject.toml
├── db.sqlite3
│
├── config/ # Django project package
│ ├── settings.py # INSTALLED_APPS, MIDDLEWARE, REFLEX_DJANGO_*
│ ├── urls.py # reflex_mount(...) — last line
│ ├── asgi.py # imports reflex_django.asgi_entry:application
│ └── wsgi.py
│
├── shop/ # a Django app
│ ├── models.py # your ORM models
│ ├── admin.py # admin registrations
│ ├── views.py # Reflex pages + state live HERE
│ ├── serializers.py # optional ReflexDjangoModelSerializer
│ └── migrations/
│
├── blog/ # another app — blog/views.py is auto-discovered
│ ├── models.py
│ └── views.py
│
└── .web/ # generated by Reflex on build (gitignore)
Three things to notice:
- One
manage.py, at the repo root. Same as any Django project. - Pages and ORM live in the same app folder.
shop/models.pyandshop/views.pyare next to each other. Your Reflex pages and your Django models naturally share that context. - No
rxconfig.py. Noshop/shop.py. Configuration lives inurls.py. The Reflex app instance is loaded fromreflex_django.django_led_app.
What goes where¶
| Location | What lives there |
|---|---|
config/settings.py |
Django apps, middleware, database, REFLEX_DJANGO_* overrides |
config/urls.py |
Your Django routes, then reflex_mount(...) at the very bottom |
config/asgi.py |
One import line pointing at reflex_django.asgi_entry:application |
{app}/models.py |
Django ORM models (unchanged from any normal Django project) |
{app}/views.py |
@template-decorated Reflex pages and AppState subclasses |
{app}/serializers.py |
Optional ReflexDjangoModelSerializer classes |
{app}/admin.py |
Django admin registrations (unchanged) |
{app}/migrations/ |
Django migrations (unchanged) |
| Not present | {app}/{app}.py — Reflex's app instance is auto-loaded |
| Not present | rxconfig.py — config lives in urls.py |
Generated and ignored paths¶
These directories are created by Reflex or manage.py run_reflex. You don't edit them, and you should .gitignore them:
| Path | What's in it |
|---|---|
.web/ |
Reflex's frontend project (the compiled React source) |
.web/build/client/ |
The compiled SPA bundle (SSR layout) |
.web/_static/ |
The compiled SPA bundle (legacy layout) |
.reflex/ |
Reflex's per-user cache (may be global on your machine) |
STATIC_ROOT/_reflex/ |
Final staging location served by ReflexMountView in production |
How pages are discovered¶
When the server boots, reflex_django.django_led_app walks every entry in INSTALLED_APPS and tries to import {app}.views. Any @template / @page decorators in those modules register their routes. There's nothing for you to wire up.
The skip list: django.* apps and reflex_django itself are never scanned.
INSTALLED_APPS = [
"django.contrib.admin", # skipped
"django.contrib.auth", # skipped
...
"reflex_django", # skipped
"shop", # shop/views.py auto-imported
"blog", # blog/views.py auto-imported
]
If {app}/views.py doesn't exist, that app is silently skipped. If it raises an import error, you'll see it.
The app_name argument¶
reflex_mount(app_name="shop") names the primary app. It's used for a few things:
- It's the Reflex
app_name, which shows up in compiled artifacts and metadata. - If
INSTALLED_APPSdiscovery is off (REFLEX_DJANGO_AUTO_DISCOVER_PAGES = False), only{app_name}.viewsis imported. - It doubles as the default app label for things like the auth pages.
If you don't pass it, reflex-django uses the folder name containing manage.py (with hyphens turned into underscores). For most projects, this default is fine and you can leave app_name off.
Alternative layout: pages in a separate package¶
Some teams prefer to keep the UI code in its own package, away from the domain code:
myproject/
├── shop/ # models, admin, business logic
│ ├── models.py
│ └── admin.py
└── frontend/ # all Reflex pages
├── pages/
│ ├── home.py
│ ├── catalog.py
│ └── account.py
└── views.py # re-exports each page
Two ways to make this work:
Option A — put frontend in INSTALLED_APPS. frontend/views.py will be auto-discovered:
Option B — list the modules explicitly in settings.py:
REFLEX_DJANGO_PAGE_PACKAGES = [
"frontend.pages.home",
"frontend.pages.catalog",
"frontend.pages.account",
]
This disables auto-discovery and uses only what you list.
Both work. Pick the one your team prefers.
A reasonable .gitignore¶
# Python
__pycache__/
*.pyc
.venv/
# Django
db.sqlite3
staticfiles/
# Reflex
.web/
.reflex/
# Optional materialized rxconfig (only present if you opt in)
rxconfig.py
Static files¶
| Environment | How it works |
|---|---|
| Development | manage.py run_reflex builds the SPA into STATIC_ROOT/_reflex/ automatically. Django's ASGIStaticFilesHandler serves both your admin assets and the SPA. |
| Production | Run python manage.py export_reflex --frontend-only --no-zip --stage-to-static-root in CI, then python manage.py collectstatic, then serve /static/ with Nginx (or your ASGI process). |
The Deployment guide has the full production setup.
Where to go next¶
- Configuration with reflex_mount() — every argument and every
REFLEX_DJANGO_*setting. - Pages live in views.py — how
@templateand route discovery work. - Architecture — the runtime picture once everything is loaded.