I'm trying to deploy my react/django web-app to a linux-VM droplet. I'm not using a webpack for the JS content. Instead, I'm serving npm run build static files through a CDN sub-domain, digital ocean s3 bucket.
I'm able to python manage.py collectstatic which then pushes my react production build folder to the CDN.
When I visit my production website, it currently just loads up a blank page with these console errors:
Refused to apply style from 'https://www.my_website_URL.com/static/css/main.ce8d6426.chunk.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
Refused to execute script from 'https://www.my_website_URL.com/static/js/2.ca12ac54.chunk.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
Refused to execute script from 'https://www.my_website_URL.com/static/js/main.220624ac.chunk.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
There aren't any network errors that provide any useful information for this matter.
The issue has to be server side (django)... I think.
Project set up:
The react production build is inside my core django folder.
Here is how I link my React through django:
core urls.py
def render_react(request):
return render(request, "index.html")
#index.html being created by react, not django templates
urlpatterns = [
re_path(r"^$", render_react),
re_path(r"^(?:.*)/?$", render_react),
...
]
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
{% comment %} <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> {% endcomment %}
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.css"
/>
<script src="//cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
settings.py
import os
from pathlib import Path
from decouple import config
import dj_database_url
from datetime import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'.
# BASE_DIR = Path(__file__).resolve().parent.parent
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('DJANGO_SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['URL's']
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_HTTPONLY = True
INSTALLED_APPS = [
'rest_framework',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third Party Apps #
'django_filters',
'corsheaders',
'django_extensions',
'drf_yasg',
'storages',
# Apps
'users',
'bucket',
'bucket_api',
#oauth
'oauth2_provider',
'social_django',
'drf_social_oauth2',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'oauth2_provider.middleware.OAuth2TokenMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'core.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS' : [os.path.join(BASE_DIR, 'build')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
],
},
},
]
WSGI_APPLICATION = 'core.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': config('DJANGO_DB_NAME'),
'USER' : config('DJANGO_DB_ADMIN'),
'PASSWORD' : config('DJANGO_ADMIN_PASS'),
'HOST' : config('DJANGO_DB_HOST'),
'PORT' : config('DJANGO_DB_PORT'),
'OPTIONS': {'sslmode':'disable'},
}
}
db_from_env = dj_database_url.config(conn_max_age=600)
DATABASES['default'].update(db_from_env)
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'America/New_York'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = config('AWS_STORAGE_BUCKET_NAME')
AWS_S3_ENDPOINT_URL = config('AWS_S3_ENDPOINT_URL')
AWS_S3_CUSTOM_DOMAIN = config('AWS_S3_CUSTOM_DOMAIN')
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400',
}
AWS_LOCATION = config('AWS_LOCATION')
AWS_DEFAULT_ACL = 'public-read'
STATIC_URL = '{}/{}/'.format(AWS_S3_ENDPOINT_URL, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static/templates'),
os.path.join(BASE_DIR, 'build/static')
]
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
How can I fix my Django to properly serve production static chunk css and js files from my CDN? The pathing and location to CDN have to be correct if the chrome console is able to locate the files within the error.
Please let me know if you need more information from my side. Currently stuck and do not have a simple solution to fix my MIME type errors and solving my website from loading only a blank page.
Thank you for any help/tips/or guidance!
If anyone is wondering, I'm using Gunicorn and Nginx.
EDIT:
added a bounty to draw attention to this question. I am not using Django webpack loader and babel. I would rather not rely on other libraries that could break things easily.
EDIT #2:
I have added my NGINX config file, should I redirect traffic to my CDN path here?
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://my_website_URL.io$request_uri;
}
server {
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
server_name my_website_URL.com www.my_website_URL.com;
# Let's Encrypt parameters
ssl_certificate /etc/letsencrypt/live/my_website_URL.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/my_website_URL.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location = /favicon.ico { access_log off; log_not_found off; }
location / {
proxy_pass http://unix:/run/gunicorn.sock;
proxy_redirect off;
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 https;
}
EDIT EDIT EDIT:
I have added my gunicorn file because I'm getting a 502 bad gateway and my gunicorn service is giving me this error:
● gunicorn.socket - gunicorn socket
Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor preset: enabled)
Active: failed (Result: service-start-limit-hit) since Wed 2021-04-28 23:44:16 UTC; 1min 2s ago
Triggers: ● gunicorn.service
Listen: /run/gunicorn.sock (Stream)
here is my gunicorn config:
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
User=alpha
Group=www-data
WorkingDirectory=/home/user/srv/project/backend
ExecStart=/home/user/srv/project/backend/venv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--timeout 300 \
--bind unix:/run/gunicorn.sock \
core.wsgi:application
[Install]
WantedBy=multi-user.target
Why your app isn't looking for your static files at your CDN
The static file URLs are being generated by React not Django. So even if STATIC_URL is set correctly, the URLs to the static files aren't being templated in Django e.g. using {% static '' %}.
According to your TEMPLATES setting Django is serving the built version of index.html from npm run build (rather than the index.html in the question). The /static URLs to your CSS and JavaScript files (also built by React) in index.html are being generated by React and not Django.
To get React to generate URLs with the correct prefix you can set the PUBLIC_URL environment variable before running npm run build. Currently it would be using the default setting (which is the root / of the host).
The errors you were receiving would have been an issue if you were serving static files on your host or using {% static '' %}. For reference:
Why you were getting a MIME type error (urls.py)
One of your URLs is too greedy:
re_path(r"^(?:.*)/?$", render_react),
This will match:
static/css/main.ce8d6426.chunk.css
So that URL will resolve to your render_react view and attempt to serve your index.html file, hence why it thinks the MIME type is text/html:
Refused to apply style from 'https://www.my_website_URL.com/static/css/main.ce8d6426.chunk.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
If you want to match every URI except those that begin with static you could use a negative lookahead in your regular expression:
re_path(r"^(?!static)(?:.*)/?$", render_react),
Why Django couldn't find your static files (settings.py)
In your settings.py you're overrwriting the STATIC_URL setting that directs Django to your CDN for static files:
STATIC_URL = '{}/{}/'.format(AWS_S3_ENDPOINT_URL, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATIC_URL = '/static/' # <------- REMOVE
In addition the STATIC_URL needs to be prepended with https:// and use AWS_S3_CUSTOM_DOMAIN (not AWS_S3_ENDPOINT_URL):
STATIC_URL = f'https://{AWS_S3_ENDPOINT_URL}/{AWS_LOCATION}/'
Why you were getting a 502 Bad Gateway error
Your NGINX and Gunicorn configurations are fine. Your Django application may not be starting correctly, for example, due to a configuration error:
To check the log:
journalctl -u gunicorn.service
It can also be useful to run a simple smoke test (does it turn on?):
./manage.py runserver
In an production environment, your static files are not served through Django, but should be served directly through the webserver.
So you should configure your webserver (I assue Nginx) to serve the content of
the static directory (aka static/css/main.ce8d6426.chunk.css) directly.
I need to get a socket.io server running using python.
I followed this example:
https://tutorialedge.net/python/python-socket-io-tutorial/
With the final files looking as follows:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<button onClick="sendMsg()">Hit Me</button>
<!--WORKS:-->
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>-->
<!-- DOESNT WORK:-->
<script src="http://localhost:8080/socket.io/socket.io.js"></script>
<script>
const socket = io("http://localhost:8080");
function sendMsg() {
socket.emit("message", "HELLO WORLD");
}
socket.on("message", function(data) {
console.log(data);
});
</script>
</body>
</html>
server.py
from aiohttp import web
import socketio
sio = socketio.AsyncServer()
app = web.Application()
sio.attach(app)
async def index(request):
with open('index.html') as f:
return web.Response(text=f.read(), content_type='text/html')
#sio.on('message')
async def print_message(sid, message):
print("Socket ID-: " , sid)
print(message)
await sio.emit('message', message[::-1])
app.router.add_get('/', index)
if __name__ == '__main__':
web.run_app(app)
So much this is only copy paste and thus works fine, but I dont want to use the script from some online source. So I tried to modify the src of the script by commenting out the running version and replacing it by a version using a local socket.io.js.
As I did not find the script on my machine I found the following questions, that both did not help me solve my issue:
node-js-socket-io-socket-io-js-not-found
socket-io-not-being-served-by-node-js-server
No matter what I do I get the following error in my browser:
GET http://localhost:8080/node_modules/socket.io/socket.io.js net::ERR_ABORTED 404 (Not Found)
(index):18 Uncaught ReferenceError: io is not defined
at (index):18
(anonymous) # (index):18
From what I understood from the 2 linked threads, my server should provide the socket.io/socket.io.js when listen is called on the server.
Unfortunately this is not happening in my case.
I had socket.io installed via pip, I also tried npm install socket.io --save as suggested, this gives me a new folder 'node_modules', but modifying the src for my script to:
<script src="http://localhost:8080/node_modules/socket.io/socket.io.js"></script>
doesnt help either.
For some reason using the fie from cdnjs works just fine (see my index.html).
I would be very glad if someone could help me out with this.
Cheers
Chris
I've never worked with AIOHTTP (always worked with flask-socketio) so I did some research using the documentation. It turns out that like a lot of http server, AIOHTTP doesn't serve static files out of the box.
You will thus need to take care of it using the add_static method (doc here).
(Note that if you want to deploy this app for production it would be better practice to use an HTTP server like Apache or Nginx).
Just add this line under your routes declarations.
app.router.add_static('/static/', path='static/')
Now you need to create a static folder in the root of your project and put your socket.io.js file inside of it.
Make sure that the socket.io.js in the version 2.2 (same as the CDN version). You can get this file by downloading it directly from the CDN URL you were using or using npm -i -s socket.io-client#2.2. You can then find the socket.io.js file in node_modules/socket.io-client/dist.
You now just need to use <script src="/static/socket.io.js"></script> in your HTML to import socket.io.
I have a sub-domain and I publish my server.js on this directory. everything work fine. but I want to running my server.js inside a directory(because I want to run my react.js project on sub directory). for example:
web.example.com/sr
web is my subdomain and sr is my directory.
but my routes not worked at all:
web.example.com/sr/user/1
I got this error message:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /sr/user/1</pre>
</body>
</html>
Should I make any changes or is there any config to do this?
You can't do deployment-level configs to serve your react app to the subdir /sr. Every call to your.domain.com/sr/* will end up in your server and pass /sr/* to it (and not simply /*.
You'll have to code your server to serve your React app. If you bundle your react app to index.html and bundle.js, you'll have to write something like this (if you're using express, which you probably do):
app.get(/\/sr\/\.js/, (req, res) => res.sendFile(`${__dirname}/bundle.js`));
app.get(/\/sr\/*/, (req, res) => res.sendFile(`${__dirname}/index.html`));
If you're using react-router, you'll have to set it up too so as to take into account the leading /sr in your URL.
I'm trying to make my existing react application progressive by adding among other things a manifest.json.
However it seems that my application is unable to find my manifest.json file as I get the above error message and Cannot GET /manifest.json when I take a look at the developer tool's network tab . This is strange since my manifest.json is located at the root of my application, exactly where the error says it cannot find the file.
I tried several things like placing a manifest.json file in every directory or introducing a json-loader in my webpack configuration but nothing worked.
Where I reference the manifest
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="manifest" href="/manifest.json" >
</head>
<body>
<div id="root" class="body"></div>
</body>
</html>
In addition I'm using webpack-manifest-plugin which creates an asset-manifest.json file in my build directory that maps my index files.
webpack.config
//...
const ManifestPlugin = require("webpack-manifest-plugin")
module.exports = {
plugins: [
new ManifestPlugin({
fileName: "asset-manifest.json"
})
//...
created asset-manifest.json
{
"index.js": "index.bundle.js",
"index.html": "index.html"
}
I hope you can help me
The problem is not only with manifest.json but with all json files. As it says here: https://stackoverflow.com/a/29633038/3231884 , you need to setup the web.config according to the following example:
<?xml version="1.0"?>
<configuration>
<system.webServer>
<staticContent>
<remove fileExtension=".json" />
<mimeMap fileExtension=".json" mimeType="application/json" />
</staticContent>
</system.webServer>
</configuration>
Afterwards, you should make 'npm run build' copy it to the build folder by following these steps (credits to Liviu Costea):
https://medium.com/hackernoon/adding-web-config-to-react-projects-3eb762dbe01f
change extension manifest
from manifest.json to manifest.txt
It should be an issue of manifest.json file.
Please show the contents of manifest.json file.
Try this, In manifest.json
the name property should be in small letters and contains no spaces.
So I've read almost every SO answer/question to this topic, but still I have many questions in my head.
First, the problem:
I have an AngularJS app with html5 enabled, so I can get rid of the '#' sign.
$locationProvider.html5Mode({ enabled: true, requireBase: true });
$locationProvider.hashPrefix('!');
This is the important part in my index.html:
<!DOCTYPE html>
<html ng-app="application" ng-controller="ApplicationController as app">
<head>
<meta name="fragment" content="!">
<title>LivingRoomArt</title>
<meta charset="UTF-8">
<base href="/index.html" />
I am communicating with a NodeJS server which is using express:
router.route('/events')
.post(authController.isAuthenticated, eventController.postEvent)
.get(eventController.getEvents);
// Register all our routes with /api
app.use('/api', router);
// Start the server
app.listen(process.env.PORT || 5000);
So, the usual problem:
After reloading, I am getting an 404 from the server. I get the concept of this here, the suggested solution everywhere:
// This route deals enables HTML5Mode by forwarding missing files to the index.html
app.all('/*', function(req, res) {
res.sendfile('index.html');
});
});
The thing is, I don't have an index.html file on my server, neither do
I want to duplicate it on my server.
So how do I tell Node to handle requests properly without storing html-files on my server?
I am hosting the Node app on Heroku, if this helps.
When you say you don't serve static files, you're saying that the node.js API isn't right?
I guess you end up with two distinct urls, let's call them http://api.com and http://client.com.
I don't understand why your API should handle the 404. Do you load http://api.com in your browser and expecting your index.html? If it's really your use-case, I would advice a simple routing to declare in your API like:
app.all('/*', function (req, res) {
res.redirect('http://client.com');
});
Which will redirect all requests not catched by your previous routes declaration to your client website.
Then, there is two options:
If the server that serves your static files is another Node.Js server using express, you could perfectly do the sendfile, since you now have access to the index.html
If you're using Nginx, (which I strongly recommend if you don't) for the statics, you could do a configuration like this to redirect all failed requests (missing files / routes) to the index.html
server {
listen 80;
root /app/www;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}