Skip to content

Add streaming support for Next.js App Router #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,25 @@ async def jobs(request):
return await render_nextjs_page(request)
```

#### Using `nextjs_page` with `stream=True` (Recommended)

If you're using the [Next.js App Router](https://nextjs.org/docs/app) (introduced in Next.js 13+), you can enable streaming by setting the `stream=True` parameter in the `nextjs_page` function. This allows the HTML response to be streamed directly from the Next.js server to the client. This approach is particularly useful for server-side rendering with streaming support to show an [instant loading state](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming#instant-loading-states) from the Next.js server while the content of a route segment loads.

Here's an example:

```python
from django_nextjs.views import nextjs_page

urlpatterns = [
path("/nextjs/page", nextjs_page(stream=True), name="nextjs_page"),
]
```

**Considerations:**

- When using `stream_nextjs_page`, you cannot use a custom HTML template in Django, as the HTML is streamed directly from the Next.js server.
- The `stream` parameter will default to `True` in future releases. Currently, it is set to `False` for backward compatibility. To avoid breaking changes, we recommend explicitly setting `stream=False` if you are customizing HTML and do not want to use streaming.

## Customizing the HTML Response

You can modify the HTML code that Next.js returns in your Django code.
Expand Down
50 changes: 49 additions & 1 deletion django_nextjs/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import aiohttp
from asgiref.sync import sync_to_async
from django.conf import settings
from django.http import HttpRequest, HttpResponse
from django.http import HttpRequest, HttpResponse, StreamingHttpResponse
from django.middleware.csrf import get_token as get_csrf_token
from django.template.loader import render_to_string
from multidict import MultiMapping
Expand Down Expand Up @@ -78,6 +78,11 @@ def _get_nextjs_response_headers(headers: MultiMapping[str]) -> Dict:
"Vary",
"Content-Type",
"Set-Cookie",
"Link",
"Cache-Control",
"Connection",
"Date",
"Keep-Alive",
],
)

Expand Down Expand Up @@ -150,3 +155,46 @@ async def render_nextjs_page(
headers=headers,
)
return HttpResponse(content=content, status=status, headers=response_headers)


async def stream_nextjs_page(
request: HttpRequest,
allow_redirects: bool = False,
headers: Union[Dict, None] = None,
):
"""
Stream a Next.js page response.
This function is used to stream the response from a Next.js server.
"""
page_path = quote(request.path_info.lstrip("/"))
params = [(k, v) for k in request.GET.keys() for v in request.GET.getlist(k)]
next_url = f"{NEXTJS_SERVER_URL}/{page_path}"

session = aiohttp.ClientSession()

try:
nextjs_response = await session.get(
next_url,
params=params,
allow_redirects=allow_redirects,
cookies=_get_nextjs_request_cookies(request),
headers=_get_nextjs_request_headers(request, headers),
)
response_headers = _get_nextjs_response_headers(nextjs_response.headers)

async def stream_nextjs_response():
try:
async for chunk in nextjs_response.content.iter_any():
yield chunk
finally:
await nextjs_response.release()
await session.close()

return StreamingHttpResponse(
stream_nextjs_response(),
status=nextjs_response.status,
headers=response_headers,
)
except:
await session.close()
raise
9 changes: 8 additions & 1 deletion django_nextjs/views.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
from typing import Dict, Union

from .render import render_nextjs_page
from .render import render_nextjs_page, stream_nextjs_page


def nextjs_page(
*,
stream: bool = False,
template_name: str = "",
context: Union[Dict, None] = None,
using: Union[str, None] = None,
allow_redirects: bool = False,
headers: Union[Dict, None] = None,
):
if stream and (template_name or context or using):
raise ValueError("When 'stream' is set to True, you should not use 'template_name', 'context', or 'using'")

async def view(request, *args, **kwargs):
if stream:
return await stream_nextjs_page(request=request, allow_redirects=allow_redirects, headers=headers)

return await render_nextjs_page(
request=request,
template_name=template_name,
Expand Down
Loading