QueraTeam / django-nextjs

Next.js integration for Django projects
MIT License
358 stars 19 forks source link
django integration nextjs

Django Next.js

Next.js integration for Django projects.

So you want to use both Django and Next.js in your project. There are two scenarios:

  1. You are starting a new project and you want to use Django as back-end and Next.js as front-end. Django only serves API requests. All front-end code lives in Next.js and you don't write any Django template.

    In this scenario you don't need this package (although you can use it). You simply start both Django and Next.js servers and point your public webserver to Next.js.

  2. You need both Django templates and Next.js at the same time and those pages should easily link to each other. Maybe you have an existing Django project which has pages rendered by Django template and want some new pages in Next.js. Or you want to migrate your front-end to Next.js but since the project is large, you need to do it gradually.

    In this scenario, this package is for you!

How does it work?

From a comment on StackOverflow:

Run 2 ports on the same server. One for Django (public facing) and one for Next.js (internal). Let Django handle all web requests. For each request, query Next.js from Django view to get HTML response. Return that exact HTML response from Django view.

Installation

Setup Next.js URLs (Development Environment)

If you're serving your site under ASGI during development, use Django Channels and add NextJSProxyHttpConsumer, NextJSProxyWebsocketConsumer to asgi.py like the following example.

Note: We recommend using ASGI and Django Channels, because it is required for fast refresh (hot module replacement) to work properly in Nextjs 12+.

import os

from django.core.asgi import get_asgi_application
from django.urls import re_path, path

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
django_asgi_app = get_asgi_application()

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django_nextjs.proxy import NextJSProxyHttpConsumer, NextJSProxyWebsocketConsumer

from django.conf import settings

# put your custom routes here if you need
http_routes = [re_path(r"", django_asgi_app)]
websocket_routers = []

if settings.DEBUG:
    http_routes.insert(0, re_path(r"^(?:_next|__next|next).*", NextJSProxyHttpConsumer.as_asgi()))
    websocket_routers.insert(0, path("_next/webpack-hmr", NextJSProxyWebsocketConsumer.as_asgi()))

application = ProtocolTypeRouter(
    {
        # Django's ASGI application to handle traditional HTTP and websocket requests.
        "http": URLRouter(http_routes),
        "websocket": AuthMiddlewareStack(URLRouter(websocket_routers)),
        # ...
    }
)

Otherwise (if serving under WSGI during development), add the following to the beginning of urls.py:

path("", include("django_nextjs.urls"))

Warning: If you are serving under ASGI, do NOT add this to your urls.py. It may cause deadlocks.

Setup Next.js URLs (Production Environment)

In production, use a reverse proxy like Nginx or Caddy:

URL Action
/_next/static/... Serve NEXTJS_PATH/.next/static directory
/_next/... Proxy to http://localhost:3000
/next/... Serve NEXTJS_PATH/public/next directory

Example config for Nginx:

location /_next/static/ {
    alias NEXTJS_PATH/.next/static/;
    expires max;
    add_header Cache-Control "public";
}
location /_next/ {
    proxy_pass  http://127.0.0.1:3000;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
location /next/ {
    alias NEXTJS_PATH/public/next/;
    expires max;
    add_header Cache-Control "public";
}

Usage

Start Next.js server:

# Development:
$ npm run dev

# Production:
$ npm run build
$ npm run start

Start by developing your pages in Next.js, Then define a Django URL for each Next.js page. Here's an example of how you can do this:

from django_nextjs.views import nextjs_page

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

Even though it's not recommended, sometimes you might need to add some custom steps before showing a Next.js page in Django. However, we advise moving this logic to Next.js to ensure it's applied even during client-side redirects. If you find yourself in this situation, you can create an asynchronous view for each page as demonstrated below:

from django_nextjs.render import render_nextjs_page

async def jobs(request):
    # Your custom logic
    return await render_nextjs_page(request)

Customizing the HTML Response

You can modify the HTML code that Next.js returns in your Django code.

Avoiding duplicate code for the navbar and footer is a common use case for this if you are using both Next.js and Django templates. Without it, you would have to write and maintain two separate versions of your navbar and footer (a Django template version and a Next.js version). However, you can simply create a Django template for your navbar and insert its code at the beginning of <body> tag returned from Next.js.

To enable this feature, you need to customize the document and root layout in Next.js and make the following adjustments:

NOTE: Currently HTML customization is not working with app router (Next.js 13+).

Read this doc and customize your Next.js document:

// pages/_document.jsx (or .tsx)
...
<body id="__django_nextjs_body">
  <div id="__django_nextjs_body_begin" />
  <Main />
  <NextScript />
  <div id="__django_nextjs_body_end" />
</body>
...

Write a Django template that extends django_nextjs/document_base.html:

{% extends "django_nextjs/document_base.html" %}

{% block head %}
  <!-- ... the content you want to place at the beginning of "head" tag ... -->
  {{ block.super }}
  <!-- ... the content you want to place at the end of "head" tag ... -->
{% endblock %}

{% block body %}
  ... the content you want to place at the beginning of "body" tag ...
  ... e.g. include the navbar template ...
  {{ block.super }}
  ... the content you want to place at the end of "body" tag ...
  ... e.g. include the footer template ...
{% endblock %}

Pass the template name to nextjs_page or render_nextjs_page:

from django_nextjs.render import render_nextjs_page
from django_nextjs.views import nextjs_page

async def jobs(request):
    return await render_nextjs_page(request, template_name="path/to/template.html")

urlpatterns = [
    path("/nextjs/page", nextjs_page(template_name="path/to/template.html"), name="nextjs_page"),
    path("/jobs", jobs, name="jobs_page")
]

Notes

Settings

Default settings:

    NEXTJS_SETTINGS = {
        "nextjs_server_url": "http://127.0.0.1:3000",
        "ensure_csrf_token": True,
    }

nextjs_server_url

The URL of Next.js server (started by npm run dev or npm run start)

ensure_csrf_token

If the user does not have a CSRF token, ensure that one is generated and included in the initial request to the Next.js server by calling Django's django.middleware.csrf.get_token. If django.middleware.csrf.CsrfViewMiddleware is installed, the initial response will include a Set-Cookie header to persist the CSRF token value on the client. This behavior is enabled by default.

When You Need to ensure_csrf_token?

You may need to issue GraphQL POST requests to fetch data in Next.js getServerSideProps. If this is the user's first request, there will be no CSRF cookie, causing the request to fail since GraphQL uses POST even for data fetching. However, as long as getServerSideProps functions are side-effect free (i.e., they don't use HTTP unsafe methods or GraphQL mutations), this should be fine from a security perspective. Read more here.

Development

References

License

MIT