Pages¶
A page is a directory under a configured page root that produces a body when its URL is requested.
This page covers the three ways a page produces that body, the priority rules between them, the render function contract, and how custom template loaders extend discovery beyond template.djx.
Overview¶
The smallest page is a folder that contains a page.py and a sibling template.djx.
A page module declares context functions, action handlers, and optional render or template attributes, and any number of ancestor layout.djx files wrap the resulting body.
Body Sources¶
A page module can supply its body through these sources.
renderfunction on the page module.Highest priority. Receives DI-resolved arguments. Returns either a string body or any
HttpResponseBasesubclass, includingStreamingHttpResponseandFileResponse.templatemodule attribute.A plain string assigned at module level. Used as the page body when no
renderfunction exists, and checked before any template loader.- Registered template loaders.
The ordered list under
NEXT_FRAMEWORK["TEMPLATE_LOADERS"].DjxTemplateLoaderis the default first entry and resolves the siblingtemplate.djx. Loaders run in declared order and the first one whosecan_loadreturnsTruesupplies the body.
A page directory may instead host only a sibling layout.djx.
That directory has no body source, and the layout chain renders with an empty body slot.
See Layout-only directories under Layouts for the wrapping rules, and note that next.E012 does not fire when a sibling layout.djx is present.
See Render Order below for the per-request sequence and the render short-circuit.
Processor ordering and STRICT_CONTEXT behaviour are documented in Context and Settings.
Multiple body sources trigger next.W043, see Priority Resolution.
Render Order¶
The body source runs before the template context is built. This is the authoritative ordering for the page render path.
The body source produces the body markup. A
renderfunction is called first with its DI-resolved arguments. Whenrenderreturns anHttpResponseBasethe response is sent verbatim and the remaining steps are skipped, so layouts and the static pipeline do not run. Atemplateattribute or a template file supplies the body string directly.The
@contextcallables are collected and run to build the template variable namespace.The body is composed through the ancestor layouts and rendered with that namespace.
A render function therefore cannot read a value that a @context callable would publish, because the callables have not run yet.
The render Function¶
The render function takes any DI-resolved parameters the resolver can fill.
The most common shape is request plus captured URL parameters and marker-driven values.
from notes.models import Report
from next.urls import DUrl
def render(request, report_id: DUrl[int]) -> str:
report = Report.objects.get(pk=report_id)
return f"<section>Report {report.title}</section>"
The return value follows a strict contract.
- String body.
The framework composes the string through the ancestor layouts. Context processors, the static collector, and the
page_renderedsignal run exactly as for atemplate.djxpage.HttpResponseBasesubclass.Returned verbatim to the client. Layout composition and static placeholder injection are skipped, which makes this the right shape for redirects, JSON APIs, streaming responses, and downloads.
- Anything else.
The framework raises
TypeErrornaming the page module so the mistake surfaces during development.
Exceptions raised inside render propagate to the Django request stack unchanged.
The template Attribute¶
Assign a string to the module-level template attribute when the body is small enough to live next to the Python code.
template = "<p>ok</p>"
A render function outranks this attribute.
When no render function exists the template attribute is consulted before any registered template loader, so a module-level template string wins over a sibling template.djx.
Use it for trivial pages where a separate template file would be noise.
The template attribute has a matching PythonTemplateLoader, documented in Pages Reference.
Template Files¶
A template.djx sibling is the default source.
The file contains the body without any HTML envelope.
<ul>
{% for note in notes %}
<li>{{ note.title }}</li>
{% endfor %}
</ul>
The layout chain wraps this body with the ancestor layout.djx files.
See Layouts for the full composition rules.
Context Functions¶
A @context decorator publishes one or more values into the template scope.
from next.pages import context
@context("notes")
def all_notes() -> list:
return list(Note.objects.all())
The framework calls the function at request time. The return value lands in the template under the configured key.
The decorator has two shapes.
- Keyed single value.
@context("name")registers the return value under that name. Templates reference it as{{ name }}.- Unkeyed dict.
@contexton a function that returns a dict merges the dict into the template scope. Useful when several values share a dependency you only want to resolve once. The unkeyed form must return a mapping, and a return annotation that is not a mapping type reports next.E029.
The inherit_context=True flag on a keyed function publishes the value to
every descendant page rather than to the declaring page alone.
See Context for that flag and the other ways to vary the decorator.
Including Values in the JS Context¶
Pass serialize=True to include the return value in window.Next.context on the client side.
The value must be JSON-encodable by the active serializer.
See Serialization for the Browser for the accepted shapes and common pitfalls.
Pass serializer= with a JsContextSerializer instance to use a per-key serializer for that value instead of the global JS_CONTEXT_SERIALIZER setting.
from next.pages import context
@context("featured", serialize=True)
def featured_payload() -> dict:
return {"id": 1, "title": "Hello"}
from next.pages import context
@context
def post_context(post: Post) -> dict[str, object]:
return {
"post": post,
"comments": post.comment_set.all(),
}
Custom Template Loaders¶
The sibling template.djx loader is one implementation of the next.pages.loaders.TemplateLoader contract.
Register additional loaders in NEXT_FRAMEWORK["TEMPLATE_LOADERS"] to support other file formats.
from pathlib import Path
import markdown
from next.pages.loaders import TemplateLoader
class MarkdownTemplateLoader(TemplateLoader):
source_name = "template.md"
def can_load(self, file_path: Path) -> bool:
return (file_path.parent / "template.md").exists()
def load_template(self, file_path: Path) -> str | None:
source = (file_path.parent / "template.md").read_text(encoding="utf-8")
return markdown.markdown(source, extensions=["fenced_code"])
def source_path(self, file_path: Path) -> Path | None:
candidate = file_path.parent / "template.md"
return candidate if candidate.exists() else None
NEXT_FRAMEWORK = {
"TEMPLATE_LOADERS": [
"next.pages.loaders.DjxTemplateLoader",
"notes.loaders.MarkdownTemplateLoader",
],
}
A user-provided NEXT_FRAMEWORK["TEMPLATE_LOADERS"] replaces the default list entirely.
Include DjxTemplateLoader explicitly when you still want sibling template.djx files to load.
Call next.pages.page.register_template(file_path, template_str) from an app’s ready() to attach an in-process template string to a page path without authoring a loader class.
The method seeds the same composed-template registry that the regular pipeline writes to, so the next render of that page serves the supplied string.
See Pages Reference for the full Page surface.
Loader Contract¶
A loader sets one class attribute, implements two required methods, and may override one optional method.
source_name.Class attribute string used by the system check to name the loader in conflict warnings.
can_load(file_path).Boolean check that decides whether the loader recognises this page.
file_pathis the absolute path to thepage.pyfile. The loader inspects the sibling directory throughfile_path.parentto look for its source.load_template(file_path).Returns the body string or
Nonefor the samepage.pypath. The framework treatsNoneas “did not match” and tries the next loader.source_path(file_path).Optional. Returns the backing file path for the cache invalidation hook. Edits to that file invalidate the composed template on the next request. The default returns
Nonefor non-file-based loaders.
Priority Resolution¶
When more than one body source applies the framework picks the highest priority one and emits a warning for the redundancy.
Source |
Resolution |
|---|---|
|
Wins outright when the page module defines |
|
Used when no |
Template loaders |
Consulted only when neither of the above applies. Loaders run in |
The template loaders have no fixed numbering between them.
DjxTemplateLoader matches the sibling template.djx, but a custom loader placed before it in TEMPLATE_LOADERS is consulted first and wins when both could load the same directory.
When a page directory declares more than one body source, next.W043 reports it. The highest-priority source is used and the others are never consulted.
Common Patterns¶
Pure Redirect Page¶
A page that always redirects elsewhere uses a render function that returns HttpResponseRedirect.
from django.http import HttpResponseRedirect
from next.urls import page_reverse
def render(request) -> HttpResponseRedirect:
return HttpResponseRedirect(page_reverse("auth/login"))
JSON Endpoint¶
Return JsonResponse from render for a JSON endpoint that still benefits from URL naming.
from django.http import JsonResponse
def render(request) -> JsonResponse:
return JsonResponse({"status": "ok"})
Streaming Response¶
Reach for StreamingHttpResponse when the body is produced incrementally, such as Server Sent Events or a large export.
from django.http import StreamingHttpResponse
from notes.models import Note
from notes.providers import DNote
def render(note: DNote[Note]) -> StreamingHttpResponse:
return StreamingHttpResponse(
event_stream(note.pk),
content_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
A synchronous generator works under WSGI and the development server.
An ASGI deployment can yield from an async generator instead.
See examples/live-polls for a worked SSE broker.
Markdown Blog Post¶
Register a MarkdownTemplateLoader in NEXT_FRAMEWORK["TEMPLATE_LOADERS"] and drop a template.md next to page.py instead of a template.djx.
The loader renders the Markdown to a body string, and that body flows through the ancestor layout chain like any other source.
The page module still supplies context functions and action handlers as usual.
The loader output passes through Django’s template engine before the layout chain renders it.
Any {{ ... }} or {% ... %} token inside the Markdown source is evaluated.
Wrap untrusted prose in {% verbatim %} blocks inside the loader, or escape the braces before returning the body, when authors should not be able to invoke template tags.
See also
The Custom Template Loaders section above for the template.md loader, and examples/markdown-blog for a working setup.
System Checks¶
The pages subsystem contributes Django system checks. The check_page_functions check inspects every page.py and reports the following.
next.E012.The page module has neither a
renderfunction nor atemplateattribute, no registered loader can produce a body, and no siblinglayout.djxwraps it.next.E013.The page module defines a
renderattribute that is not callable.next.W043.More than one body source is declared in the same directory, see Priority Resolution.
The check_context_functions check inspects every page.py for keyless @context callables.
next.E029.A keyless
@contextcallable has a return annotation that is not a mapping type.
Run them through uv run python manage.py check.
See Also¶
See also
Layouts for how the layout chain wraps the page body.
Context for @context patterns and inheritance.
Page Discovery for the discovery pipeline.
Pages Reference for the public API.