Django, evolved

next.dj

Full-stack Django apps, built faster

  • Python 3.12โ€“3.14
  • Django 5.2โ€“6.0
  • ASGI ยท WSGI

File-based routing, DJX templates, components, forms, and live partial updates on top of Django. The patterns of a modern web framework, without leaving the Django you already know.

$ uv add next.dj

Core building blocks

The patterns next.dj adds, each shown in real project source.

File router

Pages are files

Add a directory with a page.py under routes/ and it answers a URL. Bracketed segments capture typed parameters.

  • Directories become URL segments, no urls.py
  • [int:post_id] captures a typed path parameter
  • DUrl[int] coerces and injects it by type
routes/posts/[int:post_id]/page.py
from notes.models import Note
from next.pages import context
from next.urls import DUrl

@context("note")
def note(post_id: DUrl[int]) -> Note:
    return Note.objects.get(pk=post_id)
Components

Composable components

Call components by name, pass props, and fill named slots with child markup.

  • One tag, {% component %}, for every component
  • Props are explicit, slots fill block content
  • Co-locate CSS and JS in the component folder
routes/template.djx
{% #component "card" title="Welcome" %}
  {% #slot "content" %}
    <p>Latest update.</p>
  {% /slot %}
{% /component %}
Forms

Forms that register themselves

Subclass next.forms.ModelForm with no URL to define. The framework attaches the POST endpoint, CSRF, and a re-render pipeline, and a failed submission returns the page with the user's input intact.

  • No urls.py entry and no view to write
  • On a failed submit the user keeps what they typed
  • instance_from_url loads the edit row from the URL
  • FormWizard spans steps without session juggling
notes/pages/note/[slug]/page.py
import next.forms

class NoteForm(next.forms.ModelForm):
    class Meta:
        model = Note
        fields = ["title", "body"]
        instance_from_url = "slug"
Partial rendering

Update a slice, not the page

Wrap a region in {% zone %} and point a form, filter, or link at it. The server re-renders only that slice and ships the DOM patches the client applies. No endpoint, no JavaScript, and a full page load as the fallback.

  • data-next-trigger auto-submits, morph keeps focus and caret
  • The server authors every patch, selectors never cross the wire
  • Modals, toasts, and an SSE stream ride the same protocol
  • Every interaction degrades to a plain request without JS
wiki/routes/search/template.djx
<form method="get"
      data-next-target="search-results"
      data-next-trigger="input" data-next-debounce="300">
  <input name="q" value={{ query }}>
</form>

{% zone "search-results" %}
  {% for hit in results %}
    <a href={{ hit.url }}>{{ hit.title }}</a>
  {% endfor %}
{% endzone %}
Static assets

Assets next to pages

Drop a template.css or component.js beside the file that uses it. The pipeline collects each asset and injects it where the page renders.

  • Asset files pair with a page or component by filename
  • Component assets ship only when the component renders
  • {% collect_styles %} emits the deduplicated output
notes/
routes/
  page.py
  template.djx
  template.css        # collected with the page
  template.js
_components/note_card/
  component.djx
  component.css       # collected with the component
  component.js
Dependency injection

Inject by signature

Declare what a page or action needs as typed parameters. The resolver matches each one to a provider and supplies it.

  • Parameters resolve by name, type, and marker
  • @resolver.dependency publishes a named value
  • Depends("name") pulls it into any signature
routes/notes/[int:note_id]/page.py
from django.http import HttpRequest
from next.deps import Depends
from next.pages import context
from next.urls import DUrl

@context("note")
def note(
    request: HttpRequest,
    note_id: DUrl[int],
    repo: NoteRepo = Depends("note_repo"),
) -> Note:
    return repo.for_user(request.user).get(pk=note_id)
Testing

Tests that know the router

NextClient boots the file router and registers actions, so a test posts to an action by name.

  • NextClient extends Django's test client
  • post_action resolves an action to its URL
  • render_page covers template-only assertions
notes/tests/test_actions.py
from next.testing.client import NextClient

def test_create_note():
    response = NextClient().post_action(
        "note_form", {"title": "Test", "body": ""},
    )
    assert response.status_code == 302

These are the core blocks. next.dj ships more โ€” layouts, signals, autoreload, deployment, and the full reference live in the documentation.