Django + React app deployment via zappa on aws

121 Views Asked by At

I am currently deploying a django + react app on amazon aws using zappa.

Django is used as a restful api in combination with the DRF. React is hosted by django, i.e. django has a route that sends the index.html file of React as a response to all none-api URLs.

The page works as intended as long as I navigate on the page via clicking. However, when I refresh the page I do always get a 404 error. Why is this? The URL that I am located on when refreshing the page is like this:

myDomainName/currentPath

When I refresh the page, an immediate redirect is caused and the new request is made to:

myDomainName/stageName/currentPath

Therefore, the URL that I see in the browser search bar when everything is done loading is:

myDomainName/stageName/currentPath instead of myDomainName/currentPath.

As react does not know this URL with the prepended stageName, it raises a 404 error.

My question is: How can I make sure that the URL after loading is of form myDomainName/currentPath?

I think the redirect of CloudFront must happen, as its origin is simply located at path: /stageName/currentPath. Thus, I cannot change anything here.

Note: Once this problem happened once on a specific page, the next refresh works correctly as CloudFront uses cached data.


Another solution that I thought of is changing the behaviour of CloudFront. First, the request made by the user by refreshing the page is sent to CloudFront. CloudFront then prepends the stage name of the origin (the stage name identifies different origins). If CloudFront makes a request to the origin with this new pathname, everything works well at the origin. When CloudFront receives the response from the origin it could forward the data it received from the origin to the client, but to the URL requested by the user. CloudFront in turn only works as a proxy.

Any advice is warmly welcome, as it is very frustrating to have a fully functional page, which does not work correctly on page refresh.

Cheers

1

There are 1 best solutions below

1
KingRanTheMan On

Without seeing your exact code and how your file structure is set up, it is difficult to give a comprehensive answer, especially because there are so many different ways to set up Django + React to work together.

However two of the most common ways are:

(A) Deploy a standalone React app on its own server, usually using Node + Express, but deploy a second standalone Django project with a DjangoRestFramework API to model and interact with your database (usually using fetch or some other ajax library to communicate between the two). This is the method of choice if you are most familiar with React and want to predominantly utilise React and Node features, but a downside is you lose a lot of Django functionality like its built-in Auth capabilities.

(B) Deploy a Django project, but use Webpack and Babel in combination with React to build a dynamic javascript file that 'takes over' the frontend functionality. This is the method of choice if you are most familiar with Django and want as many of its built-in features as possible (like Auth), but you want to use a Javascript framework (like React) to build your frontend. This method usually still uses DRF and fetch for interactions, but Django is serving the site rather than a standalone Node server.

It sounds like your project is closer to (B) than (A), so the below fixes which I have used before may help you with your URL issues, especially if you are using react-router which does not work nicely out of the box with Django urls:

# urls.py (myproject/frontend/urls.py)

from django.urls import path
from .views import index

urlpatterns = [
    path('', index, name='frontend_index'),
    path('<path:path>/', index, name='frontend_index_with_path')
]
# views.py (myproject/frontend/views.py)

from django.shortcuts import render, redirect
from django.conf import settings
from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def index(request, *args, **kwargs):
    if not request.user.is_authenticated:
        return redirect(settings.LOGIN_URL, request.path)
    return render(request, 'frontend/index.html')
/* Example react-router set-up, in e.g. MainNavigation.js */
/* In my set-up, this would be in 
  /myproject/frontend/src/components/Navigation/MainNavigation.js 
but this will vary considerably from project to project */

import React from 'react';
import { Link } from 'react-router-dom';
import Navbar from 'react-bootstrap/Navbar';
import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import NavDropdown from 'react-bootstrap/NavDropdown';

const MainNavigation = () => {
  return (
    <Navbar bg="light" expand="lg" sticky="top">
      <Navbar.Brand href="/app/">My App</Navbar.Brand>
      <Navbar.Toggle aria-controls="basic-navbar-nav" />
      <Navbar.Collapse id="basic-navbar-nav">
        <Nav className="me-auto">
          <Nav.Link>
            <Link to="/">Home</Link>
          </Nav.Link>
          <Nav.Link>
            <Link to="/workers/list">Workers</Link>
          </Nav.Link>
          <Nav.Link>
            <Link to="/clients/list">Clients</Link>
          </Nav.Link>
          <Nav.Link>
            <Link to="/jobs/list">Jobs</Link>
          </Nav.Link>
        </Nav>
        <Nav className="ml-auto">
          <NavDropdown title="Actions" id="basic-nav-dropdown">
            <NavDropdown.Item href="http://127.0.0.1:8000/">Visit Website</NavDropdown.Item>
            <NavDropdown.Item href="http://127.0.0.1:8000/api/">Visit API</NavDropdown.Item>
            <NavDropdown.Divider />
            <NavDropdown.Item href="/app/termsandconditions">See Ts &amp; Cs</NavDropdown.Item>
          </NavDropdown>
          <Nav.Link href="http://127.0.0.1:8000/user/logout">
            Log Out
          </Nav.Link>
        </Nav>
      </Navbar.Collapse>
    </Navbar>
  );
}

export default MainNavigation;

The key line by far is path('<path:path>/', index, name='frontend_index_with_path') inside our urls.py file: it should stop your 404 error when you refresh. What happens is Django interprets the URL string sent exactly as is, and without that line it cannot map the URL to a view (because Django cannot natively interact with react-router). So this path:path is essentially a dummy variable that always dispatches the index.html file regardless of the URL string, allowing react-router to take control from there.

SaaSPegasus has much more information on both this trick and other common issues when integrating Django + React.

I also recommend the Tech with Tim tutorial series on this, but please note he uses an old version of React Router which may not be compatible with your build.

Hope this helps