You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

部署Django/React应用时从AWS加载CSS/JS出现MIME类型错误求助

Django+React部署Heroku后React路由静态文件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格式(当前配置已满足),避免重定向过程中返回错误内容。


验证步骤

  1. 重新构建React并推送代码到Heroku
  2. 访问React路由页面,查看网络面板:静态文件请求应指向S3域名,返回200状态码
  3. 检查控制台是否还有MIME类型错误提示

内容的提问来源于stack exchange,提问作者Stephane-Gon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 05:10:38