Apache部署Django生产环境静态文件加载失败求助
环境信息
- Django版本:3.0
- Python版本:3.8
- Web服务器:Apache2
- 操作系统:Ubuntu
问题表现
开发模式DEBUG=True时静态文件正常加载,React内容显示正常;生产环境无论DEBUG设为True还是False,都出现以下问题:
- JavaScript、CSS文件返回404错误
- 文件以
text/html类型返回,触发MIME类型校验错误
浏览器控制台错误日志:
GET https://www.myapp.com/static/js/main.a14a4a4b.js net::ERR_ABORTED 404 (Not Found) Refused to execute script from 'https://www.myapp.com/static/js/main.a14a4a4b.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled. Refused to apply style from 'https://www.myapp.com/static/css/main.868dfa48.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled. GET https://www.myapp.com/favicon.png 404 (Not Found) GET https://www.myapp.com/manifest.json 404 (Not Found) Manifest: Line: 1, column: 1, Syntax error.
静态文件目录结构
/var/www/jmyapp/staticfiles: asset-manifest.json css favicon.png index.html js logo192.png logo512.png manifest.json media robots.txt sitemap.xml static /var/www/jmyapp/staticfiles/css: main.868dfa48.css main.868dfa48.css.map /var/www/jmyapp/staticfiles/js: main.a14a4a4b.js main.a14a4a4b.js.LICENSE.txt main.a14a4a4b.js.map /var/www/jmyapp/staticfiles/media: MyAppSwap.7be50f5ecb7b614c38c9.png eth.df265e367364f285053a1285ad8d418d.svg /var/www/jmyapp/staticfiles/static: css js media /var/www/jmyapp/staticfiles/static/css: main.868dfa48.css main.868dfa48.css.map /var/www/jmyapp/staticfiles/static/js: main.a14a4a4b.js main.a14a4a4b.js.LICENSE.txt main.a14a4a4b.js.map /var/www/jmyapp/staticfiles/static/media: MyAppSwap.7be50f5ecb7b614c38c9.png eth.df265e367364f285053a1285ad8d418d.svg
关键配置文件
settings.py核心配置
import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) REACT_APP_DIR = os.path.join(BASE_DIR, 'myappswap', 'dex', 'build') STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), os.path.join(REACT_APP_DIR, "static"), ] DEBUG = False TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BASE_DIR, 'templates'), REACT_APP_DIR, ], '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', ], }, }, ] WSGI_APPLICATION = 'mysite.wsgi.application'
Apache配置(default-ssl.conf)
<IfModule mod_ssl.c> <VirtualHost _default_:443> ServerName www.myapp.com ServerAlias myapp.com DocumentRoot /var/www/jmyapp WSGIDaemonProcess mysite python-path=/var/www/jmyapp/mysite_env/lib/python3.8/site-packages WSGIProcessGroup mysite WSGIScriptAlias / /var/www/jmyapp/mysite/wsgi.py Alias /robots.txt /var/www/jmyapp/staticfiles/robots.txt Alias /favicon.ico /var/www/jmyapp/staticfiles/favicon.png Alias /static/ /var/www/jmyapp/staticfiles/ Alias /media/ /var/www/jmyapp/media/ ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on SSLProtocol -ALL +TLSv1.2 SSLCertificateFile /etc/letsencrypt/live/myapp.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/myapp.com/privkey.pem <IfModule mod_headers.c> <Location "/geconame/"> SetEnvIf Origin "http(s)?://(www\.)?(localhost|www\.myapp\.com)$" ACAO=$0 Header set Access-Control-Allow-Origin %{ACAO}e env=ACAO Header merge Vary Origin </Location> </IfModule> <FilesMatch "\.(cgi|shtml|phtml|php)$"> SSLOptions +StdEnvVars </FilesMatch> <Directory /usr/lib/cgi-bin> SSLOptions +StdEnvVars </Directory> <Directory /var/www/jmyapp> Require all granted </Directory> <Directory /var/www/jmyapp/staticfiles> Require all granted </Directory> <Directory /var/www/jmyapp/media> Require all granted </Directory> <Directory /var/www/jmyapp/mysite> <Files wsgi.py> Require all granted </Files> </Directory> BrowserMatch "MSIE [2-6]" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 </VirtualHost> </IfModule>
Apache配置(mysite.conf)
<VirtualHost *:80> ServerName www.myapp.com ServerAlias myapp.com ErrorLog /var/log/apache2/error.log CustomLog /var/log/apache2/access.log combined <IfModule mod_headers.c> <Location "/geconame/"> SetEnvIf Origin "http(s)?://(www\.)?(localhost|www\.myapp\.com)$" ACAO=$0 Header set Access-Control-Allow-Origin %{ACAO}e env=ACAO Header merge Vary Origin </Location> </IfModule> WSGIDaemonProcess jmyapp python-path=/var/www/jmyapp/mysite_env/lib/python3.8/site-packages WSGIProcessGroup jmyapp WSGIScriptAlias / /var/www/jmyapp/mysite/wsgi.py Alias /robots.txt /var/www/jmyapp/staticfiles/robots.txt Alias /favicon.ico /var/www/jmyapp/staticfiles/favicon.png Alias /static/ /var/www/jmyapp/staticfiles/ Alias /media/ /var/www/jmyapp/media/ <Directory /var/www/jmyapp> Require all granted </Directory> <Directory /var/www/jmyapp/staticfiles> Require all granted </Directory> <Directory /var/www/jmyapp/media> Require all granted </Directory> <Directory /var/www/jmyapp/mysite> <Files wsgi.py> Require all granted </Files> </Directory> <Directory /var/www/jmyapp/staticfiles> Require all granted </Directory> </VirtualHost>
URL配置(main/urls.py)
from django.urls import path from . import views from django.contrib.staticfiles.storage import staticfiles_storage app_name = 'main' urlpatterns = [ path('', views.mappage, name='mappage'), path('coins/<cryptoname>', views.coinspage, name='coinspage'), path('prices/<number>', views.prices, name='prices'), path('new_prices/<number>', views.new_prices, name='new_prices'), path('geconame/', views.get_coin, name='coin_name'), path('api/crypto-data/', views.crypto_data_api, name='crypto_data_api'), path('api/socialinfo/', views.socialinfo, name='socialinfo'), path('web3/tokenPrice/', views.token_price, name='token_price'), path('web3/dexSwap/', views.dex_swap, name='dex_swap'), path('web3/broadcastTransaction/', views.broadcast_transaction, name='broadcast_transaction'), ]
URL配置(mysite/urls.py)
from django.contrib import admin from django.urls import path, include from django.views.generic import TemplateView from django.conf import settings from django.conf.urls.static import static urlpatterns = [ path('', include('main.urls')), path('admin/', admin.site.urls), path('blog/', include('blogs.urls')), path('swap/', TemplateView.as_view(template_name='index.html'), name='swap'), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
package.json配置
"name": "dex", "version": "0.1.0", "private": true, "dependencies": { "@ant-design/icons": "^5.0.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "antd": "^5.1.5", "axios": "^1.2.3", "browserify-zlib": "^0.2.0", "cors": "^2.8.5", "crypto-browserify": "^3.12.0", "ethers": "^5.7.2", "node-fetch": "^2.7.0", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "querystring-es3": "^0.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.6.2", "react-scripts": "5.0.1", "stream-browserify": "^3.0.0", "url": "^0.11.3", "wagmi": "^0.10.11", "web-vitals": "^2.1.4", "web3": "^4.9.0", "yesno": "^0.4.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "eslint-config-airbnb": "^19.0.4", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-react": "^7.34.2" }, "proxy": "http://localhost:8000" }
已尝试的解决操作
- 修正文件权限:
sudo chown -R www-data:www-data /var/www/jmyapp/staticfiles sudo chmod -R 755 /var/www/jmyapp/staticfiles - 执行
collectstatic收集静态文件:python manage.py collectstatic - 重启Apache服务:
sudo systemctl restart apache2 - 验证静态文件存在于指定目录
- 设置React的BrowserRouter basename为"/"
核心疑问
- 为何生产环境中静态文件无法被找到?
- 如何解决MIME类型错误?
- Apache配置或Django设置是否存在问题?
问题分析与解决方案
1. 静态文件404的核心原因
从目录结构看,collectstatic执行后,React构建的静态文件被存到了/var/www/jmyapp/staticfiles/static/下(比如static/js/main.a14a4a4b.js),但浏览器请求的是/static/js/main.a14a4a4b.js,而Apache的Alias /static/ /var/www/jmyapp/staticfiles/会把请求映射到/var/www/jmyapp/staticfiles/js/main.a14a4a4b.js——这个路径不存在,实际文件在staticfiles/static/js/里,所以返回404。
当Apache找不到静态文件时,会把请求转发给Django的WSGI应用,Django返回404页面(text/html类型),这就是MIME类型错误的根源。
2. 针对性修复步骤
(1)调整Django的STATICFILES_DIRS配置
当前STATICFILES_DIRS包含了REACT_APP_DIR/static,导致collectstatic时把React的静态文件复制到STATIC_ROOT/static/下。修改配置,直接把React的build目录加入STATICFILES_DIRS,这样collectstatic会把build下的所有文件(包括根目录的favicon、manifest)复制到STATIC_ROOT:
STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), REACT_APP_DIR, # 替换原来的REACT_APP_DIR/static ]
修改后重新执行:
python manage.py collectstatic
此时staticfiles/下会直接有js、css目录,根路径的favicon、manifest也会被正确收集。
(2)修正Apache的Alias配置
确保静态文件的Alias指向正确的STATIC_ROOT:
# 保留原有配置,无需修改静态路径Alias Alias /static/ /var/www/jmyapp/staticfiles/ # 补充根路径文件的映射 Alias /favicon.png /var/www/jmyapp/staticfiles/favicon.png Alias /manifest.json /var/www/jmyapp/staticfiles/manifest.json
(3)移除Django urls中的static辅助配置
当DEBUG=False时,django.conf.urls.static.static()不会生效,静态文件完全由Apache处理,可从mysite/urls.py中移除这部分:
urlpatterns = [ path('', include('main.urls')), path('admin/', admin.site.urls), path('blog/', include('blogs.urls')), path('swap/', TemplateView.as_view(template_name='index.html'), name='swap'), ]
(4)重启Apache服务
sudo systemctl restart apache2
3. 验证修复效果
- 检查浏览器控制台,确认静态文件请求返回200
- 确认MIME类型错误消失,JS、CSS正常加载
- 验证favicon.png和manifest.json可正常访问
内容的提问来源于stack exchange,提问作者Brapu Coin

