如何通过 Django 正确地服务我的 React 生产构建。当前配置当前存在 MIME 类型问题

woo*_*oof 4 javascript python django reactjs digital-ocean

我正在尝试将我的 React/django Web 应用程序部署到 linux-VM Droplet。我没有使用 webpack 来处理 JS 内容。相反,我npm run build通过 CDN 子域、数字海洋 s3 存储桶提供静态文件。

\n

我能够将python manage.py collectstatic我的 React 生产构建文件夹推送到 CDN。

\n

当我访问我的生产网站时,它当前仅加载一个空白页面,其中包含以下控制台错误:

\n
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.\n\nRefused 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.\n\nRefused 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.\n
Run Code Online (Sandbox Code Playgroud)\n

没有任何网络错误为此事提供任何有用的信息。

\n

问题必须是服务器端(django)......我认为。

\n
\n

项目设立:

\n

在此输入图像描述

\n

React 生产版本位于我的核心 django 文件夹内。

\n

以下是我如何通过 django 链接 React:

\n

core urls.py

\n
def render_react(request):\n    return render(request, "index.html") \n    #index.html being created by react, not django templates \n    \nurlpatterns = [\n   re_path(r"^$", render_react),\n   re_path(r"^(?:.*)/?$", render_react),\n   ...\n]\n
Run Code Online (Sandbox Code Playgroud)\n

索引.html

\n
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.\n\nRefused 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.\n\nRefused 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.\n
Run Code Online (Sandbox Code Playgroud)\n

设置.py

\n
import os\n\n\nfrom pathlib import Path\nfrom decouple import config\nimport dj_database_url\n\nfrom datetime import timedelta\n\n# Build paths inside the project like this: BASE_DIR / \'subdir\'.\n# BASE_DIR = Path(__file__).resolve().parent.parent\nBASE_DIR = os.path.dirname(os.path.abspath(__file__))\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = config(\'DJANGO_SECRET_KEY\')\n\n# SECURITY WARNING: don\'t run with debug turned on in production!\nDEBUG = True\n\nALLOWED_HOSTS = [\'URL\'s\']\n\nSECURE_PROXY_SSL_HEADER = (\'HTTP_X_FORWARDED_PROTO\', \'https\')\nSESSION_COOKIE_SECURE = True\nCSRF_COOKIE_SECURE = True\nSECURE_SSL_REDIRECT = True\nSESSION_COOKIE_HTTPONLY = True\n\n\nINSTALLED_APPS = [\n    \'rest_framework\',\n    \'django.contrib.admin\',\n    \'django.contrib.auth\',\n    \'django.contrib.contenttypes\',\n    \'django.contrib.sessions\',\n    \'django.contrib.messages\',\n    \'django.contrib.staticfiles\',\n\n\n    # Third Party Apps #\n    \'django_filters\',\n    \'corsheaders\',\n    \'django_extensions\',\n    \'drf_yasg\',\n    \'storages\',\n\n\n    # Apps\n    \'users\',\n    \'bucket\',\n    \'bucket_api\',\n    \n    #oauth\n    \'oauth2_provider\',\n    \'social_django\',\n    \'drf_social_oauth2\',\n]\n\nMIDDLEWARE = [\n    \'django.middleware.security.SecurityMiddleware\',\n    \'django.contrib.sessions.middleware.SessionMiddleware\',\n    \'corsheaders.middleware.CorsMiddleware\',\n    \'django.middleware.common.CommonMiddleware\',\n    \'django.middleware.csrf.CsrfViewMiddleware\',\n    \'oauth2_provider.middleware.OAuth2TokenMiddleware\',\n    \'django.contrib.auth.middleware.AuthenticationMiddleware\',\n    \'django.contrib.messages.middleware.MessageMiddleware\',\n    \'django.middleware.clickjacking.XFrameOptionsMiddleware\',\n]\n\nROOT_URLCONF = \'core.urls\'\n\n\nTEMPLATES = [\n    {\n        \'BACKEND\': \'django.template.backends.django.DjangoTemplates\',\n        \'DIRS\' : [os.path.join(BASE_DIR, \'build\')],\n        \'APP_DIRS\': True,\n        \'OPTIONS\': {\n            \'context_processors\': [\n                \'django.template.context_processors.debug\',\n                \'django.template.context_processors.request\',\n                \'django.contrib.auth.context_processors.auth\',\n                \'django.contrib.messages.context_processors.messages\',\n                \'social_django.context_processors.backends\',\n                \'social_django.context_processors.login_redirect\',\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = \'core.wsgi.application\'\n\nDATABASES = {\n    \'default\': {\n        \'ENGINE\': \'django.db.backends.postgresql_psycopg2\',\n        \'NAME\': config(\'DJANGO_DB_NAME\'),\n        \'USER\' : config(\'DJANGO_DB_ADMIN\'),\n        \'PASSWORD\' : config(\'DJANGO_ADMIN_PASS\'),\n        \'HOST\' : config(\'DJANGO_DB_HOST\'),\n        \'PORT\' : config(\'DJANGO_DB_PORT\'),\n        \'OPTIONS\': {\'sslmode\':\'disable\'},\n    }\n}\n\n\ndb_from_env = dj_database_url.config(conn_max_age=600)\nDATABASES[\'default\'].update(db_from_env)\n\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        \'NAME\': \'django.contrib.auth.password_validation.UserAttributeSimilarityValidator\',\n    },\n    {\n        \'NAME\': \'django.contrib.auth.password_validation.MinimumLengthValidator\',\n    },\n    {\n        \'NAME\': \'django.contrib.auth.password_validation.CommonPasswordValidator\',\n    },\n    {\n        \'NAME\': \'django.contrib.auth.password_validation.NumericPasswordValidator\',\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/3.1/topics/i18n/\n\nLANGUAGE_CODE = \'en-us\'\n\nTIME_ZONE = \'America/New_York\'\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/3.1/howto/static-files/\n\nAWS_ACCESS_KEY_ID = config(\'AWS_ACCESS_KEY_ID\')\nAWS_SECRET_ACCESS_KEY = config(\'AWS_SECRET_ACCESS_KEY\')\nAWS_STORAGE_BUCKET_NAME = config(\'AWS_STORAGE_BUCKET_NAME\')\nAWS_S3_ENDPOINT_URL = config(\'AWS_S3_ENDPOINT_URL\')\nAWS_S3_CUSTOM_DOMAIN = config(\'AWS_S3_CUSTOM_DOMAIN\')\nAWS_S3_OBJECT_PARAMETERS = {\n    \'CacheControl\': \'max-age=86400\',\n}\nAWS_LOCATION = config(\'AWS_LOCATION\')\nAWS_DEFAULT_ACL = \'public-read\'\n\n\nSTATIC_URL = \'{}/{}/\'.format(AWS_S3_ENDPOINT_URL, AWS_LOCATION)\nSTATICFILES_STORAGE = \'storages.backends.s3boto3.S3Boto3Storage\'\nDEFAULT_FILE_STORAGE = \'storages.backends.s3boto3.S3Boto3Storage\'\n\n\nSTATIC_URL = \'/static/\'\nSTATICFILES_DIRS = [\n    os.path.join(BASE_DIR, \'static/templates\'),\n    os.path.join(BASE_DIR, \'build/static\')\n]\n\nSTATIC_ROOT = os.path.join(BASE_DIR, \'static\')\n
Run Code Online (Sandbox Code Playgroud)\n
\n

如何修复 Django 以正确地从 CDN 提供生产静态块 css 和 js 文件?如果 Chrome 控制台能够找到错误中的文件,则 CDN 的路径和位置必须正确。

\n

如果您需要我提供更多信息,请告诉我。目前陷入困境,没有一个简单的解决方案来修复我的 MIME 类型错误并解决我的网站仅加载空白页面的问题。

\n

感谢您的任何帮助/提示/或指导!

\n

如果有人想知道,我正在使用 Gunicorn 和 Nginx。

\n

编辑:\n添加了赏金以引起人们对这个问题的注意。我没有使用 Django webpack 加载器和 babel。我宁愿不依赖其他可能轻易破坏事情的库。

\n

编辑#2:\n我已经添加了 NGINX 配置文件,我应该将流量重定向到此处的 CDN 路径吗?

\n
server {\n    listen 80 default_server;\n    listen [::]:80 default_server;\n    server_name _;\n    return 301 https://my_website_URL.io$request_uri;\n}\nserver {\n    listen [::]:443 ssl ipv6only=on;\n    listen 443 ssl;\n    server_name my_website_URL.com www.my_website_URL.com;\n\n   #  Let\'s Encrypt parameters\n    ssl_certificate /etc/letsencrypt/live/my_website_URL.com/fullchain.pem; # managed by Certbot\n    ssl_certificate_key /etc/letsencrypt/live/my_website_URL.com/privkey.pem; # managed by Certbot\n    include /etc/letsencrypt/options-ssl-nginx.conf;\n    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;\n\n    location = /favicon.ico { access_log off; log_not_found off; }\n\n\n\n\n    location / {\n        proxy_pass         http://unix:/run/gunicorn.sock;\n        proxy_redirect     off;\n\n        proxy_set_header   Host              $http_host;\n        proxy_set_header   X-Real-IP         $remote_addr;\n        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;\n        proxy_set_header   X-Forwarded-Proto https;\n        }\n
Run Code Online (Sandbox Code Playgroud)\n

编辑编辑编辑:\n我添加了我的gunicorn 文件,因为我收到了 502 错误网关,并且我的gunicorn 服务给了我这个错误:

\n
\xe2\x97\x8f gunicorn.socket - gunicorn socket\n     Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor preset: enabled)\n     Active: failed (Result: service-start-limit-hit) since Wed 2021-04-28 23:44:16 UTC; 1min 2s ago\n   Triggers: \xe2\x97\x8f gunicorn.service\n     Listen: /run/gunicorn.sock (Stream)\n
Run Code Online (Sandbox Code Playgroud)\n

这是我的gunicorn配置:

\n
[Unit]\nDescription=gunicorn daemon\nRequires=gunicorn.socket\nAfter=network.target\n\n[Service]\nUser=alpha\nGroup=www-data\nWorkingDirectory=/home/user/srv/project/backend\nExecStart=/home/user/srv/project/backend/venv/bin/gunicorn \\\n          --access-logfile - \\\n          --workers 3 \\\n          --timeout 300 \\\n          --bind unix:/run/gunicorn.sock \\\n          core.wsgi:application\n\n[Install]\nWantedBy=multi-user.target\n\n
Run Code Online (Sandbox Code Playgroud)\n

ely*_*yas 6

为什么您的应用不在 CDN 上查找静态文件

静态文件 URL 是由 React 而不是 Django 生成的。因此,即使STATIC_URL设置正确,静态文件的 URL 也不会在 Django 中进行模板化,例如使用{% static '' %}.

根据您的设置,Django 正在提供fromTEMPLATES的构建版本(而不是问题中的)。CSS 和 JavaScript 文件(也由 React 构建)的 URL是由 React 而不是 Django 生成的。index.htmlnpm run buildindex.html/staticindex.html

要让 React 生成具有正确前缀的 URL,您可以PUBLIC_URL在运行之前设置环境变量npm run build。目前它将使用默认设置(即/主机的根目录)。

如果您在主机上提供静态文件或使用{% static '' %}. 以供参考:

为什么您收到 MIME 类型错误 (urls.py)

您的网址之一太贪婪了:

re_path(r"^(?:.*)/?$", render_react),
Run Code Online (Sandbox Code Playgroud)

这将匹配:

static/css/main.ce8d6426.chunk.css
Run Code Online (Sandbox Code Playgroud)

这样该 URL 将解析为您的render_react视图并尝试为您的文件提供服务index.html,因此它认为 MIME 类型是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.
Run Code Online (Sandbox Code Playgroud)

如果您想匹配除以 开头的 URI 之外的所有 URI,static您可以在正则表达式中使用否定前瞻:

re_path(r"^(?!static)(?:.*)/?$", render_react),
Run Code Online (Sandbox Code Playgroud)

为什么 Django 找不到你的静态文件(settings.py)

在 settings.py 中,您将覆盖STATIC_URL将 Django 定向到静态文件的 CDN 的设置:

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
Run Code Online (Sandbox Code Playgroud)

此外,STATIC_URL需要在前面加上https://并使用AWS_S3_CUSTOM_DOMAIN(不是AWS_S3_ENDPOINT_URL):

STATIC_URL = f'https://{AWS_S3_ENDPOINT_URL}/{AWS_LOCATION}/'
Run Code Online (Sandbox Code Playgroud)

为什么您会收到 502 Bad Gateway 错误

您的 NGINX 和 Gunicorn 配置良好。您的 Django 应用程序可能无法正确启动,例如,由于配置错误:

检查日志:

journalctl -u gunicorn.service
Run Code Online (Sandbox Code Playgroud)

运行简单的冒烟测试也很有用(它会打开吗?):

./manage.py runserver
Run Code Online (Sandbox Code Playgroud)