部署Django/React应用时从AWS加载CSS/JS出现MIME类型错误求助
问题描述
在Heroku部署Django-Rest+React应用,静态文件与媒体文件托管于AWS S3存储桶。API和Admin后台访问正常,但访问React路由页面时出现MIME类型错误:JS、CSS文件请求返回301状态码,控制台提示返回内容的MIME类型为text/html,不符合样式表/脚本的类型要求。
错误信息
Refused to apply style from 'https://app.herokuapp.com/static/css/main.9d3ee958.css/' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
app.herokuapp.com/:1 Refused to execute script from 'https://app.herokuapp.com/static/js/main.3b833115.js/' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
核心现象
- Admin后台的静态文件正常从S3存储桶加载(返回200状态码)
- React页面的静态文件请求指向Heroku应用域名而非S3,且重定向后返回HTML内容
相关配置代码
生产环境settings.py
from decouple import config import django_heroku import dj_database_url from .base import * SECRET_KEY = config('SECRET_KEY') DEBUG = False ALLOWED_HOSTS = ['*'] ROOT_URLCONF = 'portfolio.urls_prod' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BASE_DIR, 'frontend/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', ], }, }, ] # DATABASE DATABASES = {} DATABASES['default'] = dj_database_url.config(conn_max_age=600) # HEROKU django_heroku.settings(locals()) # AWS S3 SETTINGS 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_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME AWS_DEFAULT_ACL = 'public-read' AWS_S3_OBJECT_PARAMETERS = { 'CacheControl': 'max-age=86400', } AWS_HEADERS = { 'Access-Control-Allow-Origin': '*', } AWS_QUERYSTRING_AUTH = False # AWS STATIC SETTINGS AWS_LOCATION = 'static' STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION) STATICFILES_STORAGE = 'portfolio.storage_backend.StaticStorage' # AWS MEDIA SETTINGS DEFAULT_FILE_STORAGE = 'portfolio.storage_backend.MediaStorage' MEDIA_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, 'media') STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'frontend/build/static'), ] # HEROKU LOGGING DEBUG_PROPAGATE_EXCEPTIONS = True LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s", 'datefmt' : "%d/%b/%Y %H:%M:%S" }, 'simple': { 'format': '%(levelname)s %(message)s' }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', }, }, 'loggers': { 'MYAPP': { 'handlers': ['console'], 'level': 'DEBUG', }, } } # HTTPS SETTING SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_SSL_REDIRECT = True # HSTS SETTINGS SECURE_HSTS_SECONDS = 31536000 # 1 year SECURE_HSTS_PRELOAD = True SECURE_HSTS_INCLUDE_SUBDOMAINS = True options = DATABASES['default'].get('OPTIONS', {}) options.pop('sslmode', None)
storage_backend.py
from storages.backends.s3boto3 import S3Boto3Storage class MediaStorage(S3Boto3Storage): location = 'media' file_overwrite = False class StaticStorage(S3Boto3Storage): location = 'static' default_acl = 'public-read'
urls_prod.py
from django.contrib import admin from django.urls import path, re_path, include from django.views.generic import TemplateView from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), path('api/', include('api.urls')), re_path(r'^(?P<path>.*)/$', TemplateView.as_view(template_name='index.html')), path('', TemplateView.as_view(template_name='index.html')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
S3存储桶策略
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPublicRead", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket-name/*" } ] }
解决方案
1. 修正React静态文件引用路径
React构建后的index.html中静态文件为相对路径,导致请求指向Heroku域名而非S3。需配置React生成绝对路径:
- 在React项目的
package.json中添加homepage字段,值为S3静态文件根URL:"homepage": "https://bucket-name.s3.amazonaws.com/static" - 重新执行
npm run build构建React项目,再推送代码到Heroku。
2. 调整Django URL路由优先级
当前通配符路由会捕获所有带斜杠的请求(包括静态文件),需优先处理静态文件请求:
修改urls_prod.py,将静态文件路由放在最前面:
from django.contrib import admin from django.urls import path, re_path, include from django.views.generic import TemplateView from django.conf import settings from django.views.static import serve urlpatterns = [ path("admin/", admin.site.urls), path('api/', include('api.urls')), # 优先匹配静态文件请求,转发至S3 re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}), # React路由兜底规则放在最后 re_path(r'^.*$', TemplateView.as_view(template_name='index.html')), ]
3. 确保静态文件同步到S3
执行Django静态文件收集命令,将React静态文件上传至S3:
python manage.py collectstatic --noinput
在Heroku的Procfile中添加release阶段命令,实现自动执行:
release: python manage.py collectstatic --noinput web: gunicorn portfolio.wsgi
4. 验证HTTPS配置
SECURE_SSL_REDIRECT = True会强制HTTP转HTTPS,需确保S3静态文件URL为HTTPS格式(当前配置已满足),避免重定向过程中返回错误内容。
验证步骤
- 重新构建React并推送代码到Heroku
- 访问React路由页面,查看网络面板:静态文件请求应指向S3域名,返回200状态码
- 检查控制台是否还有MIME类型错误提示
内容的提问来源于stack exchange,提问作者Stephane-Gon

