解决 FastAPI `/docs` 文档白屏或加载时间过长的问题

解决 FastAPI /docs 文档白屏或加载时间过长的问题

一、问题现象

在使用 FastAPI 开发接口服务时,默认会提供一个非常方便的接口文档页面:

text 复制代码
http://127.0.0.1:8000/docs

正常情况下,访问该地址会打开 Swagger UI 文档页面,可以直接查看接口、调试接口、查看请求参数和响应结构。

但是在实际部署环境中,经常会遇到以下问题:

  1. /docs 页面打开后一直白屏;
  2. 页面长时间停留在加载状态;
  3. 浏览器控制台提示 JS 或 CSS 加载失败;
  4. 内网环境、服务器环境、无外网环境下 /docs 无法正常显示;
  5. 页面 HTML 能返回,但 swagger-ui-bundle.js 加载非常慢;
  6. 线上服务器访问 /docs 比本地慢很多。

这些问题通常不是 FastAPI 接口本身异常,而是 Swagger UI 所依赖的前端静态资源加载失败或加载过慢导致的。


二、问题原因分析

FastAPI 的 /docs 页面本质上是一个 Swagger UI 前端页面。

这个页面并不是只有一段 HTML,它还需要加载一些前端资源,例如:

text 复制代码
swagger-ui-bundle.js
swagger-ui.css

如果这些资源无法正常加载,页面就可能出现白屏。

常见原因包括:

1. CDN 资源访问慢或无法访问

FastAPI 默认文档页面通常会引用外部的 Swagger UI 静态资源。

如果服务器或浏览器访问外部 CDN 较慢,就会导致 /docs 页面长时间等待。

在部分内网环境、企业服务器、国内网络环境中,CDN 资源可能会出现:

text 复制代码
pending
timeout
ERR_CONNECTION_TIMED_OUT
ERR_CONNECTION_RESET
net::ERR_FAILED

这时 /docs 页面虽然已经打开,但核心 JS 文件没有加载完成,所以页面会一直白屏。


2. 服务器环境不能访问外网

有些 FastAPI 服务部署在内网服务器、Docker 容器、政企内网或无公网环境中。

这种情况下,接口服务本身可以正常访问,但是 /docs 页面依赖的外部 JS 和 CSS 文件无法加载。

最终表现就是:

text 复制代码
接口正常
/openapi.json 正常
/docs 白屏

3. swagger-ui-bundle.js 文件过大,下载时间过长

swagger-ui-bundle.js 是 Swagger UI 的核心 JS 文件,体积相对较大。

如果服务器没有开启缓存、压缩,或者网络质量较差,每次打开 /docs 都重新下载这个 JS 文件,就会造成页面打开缓慢。


4. 静态文件路径配置错误

如果已经改成了本地静态资源,但是路径配置错误,也会导致白屏。

例如代码中配置了:

python 复制代码
swagger_js_url="/static/swagger-ui/swagger-ui-bundle.js"
swagger_css_url="/static/swagger-ui/swagger-ui.css"

但是项目目录中没有对应文件,或者 FastAPI 没有正确挂载 /static 静态目录,就会导致 JS/CSS 返回 404。


三、排查方法

遇到 /docs 白屏时,建议先打开浏览器开发者工具。

快捷键:

text 复制代码
F12

然后切换到:

text 复制代码
Network

刷新 /docs 页面,重点检查以下资源:

text 复制代码
swagger-ui-bundle.js
swagger-ui.css
/openapi.json

重点看这几个字段:

text 复制代码
Status
Type
Size
Time

如果出现下面情况,就基本可以确定是静态资源问题:

text 复制代码
swagger-ui-bundle.js  404
swagger-ui-bundle.js  pending
swagger-ui-bundle.js  timeout
swagger-ui.css        404
/openapi.json         正常,但页面白屏

四、解决思路

最稳定的解决方案是:

text 复制代码
不要让 /docs 页面依赖外部 CDN
而是把 swagger-ui-bundle.js 和 swagger-ui.css 下载到项目本地
然后通过 FastAPI 的 StaticFiles 提供本地静态资源

这样 /docs 页面加载时,JS 和 CSS 都从自己的服务中读取,不再依赖外部网络。


五、项目目录结构

可以在项目中创建如下目录:

text 复制代码
project/
├── main.py
└── static/
    └── swagger-ui/
        ├── swagger-ui-bundle.js
        ├── swagger-ui.css
        └── favicon.png

其中核心文件是:

text 复制代码
swagger-ui-bundle.js
swagger-ui.css

favicon.png 不是必须的,只是用于设置文档页面图标。


六、FastAPI 代码配置

示例代码如下:

python 复制代码
from fastapi import FastAPI
from fastapi.openapi.docs import (
    get_swagger_ui_html,
    get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles


app = FastAPI(
    title="My API",
    docs_url=None,
    redoc_url=None,
)

app.mount(
    "/static",
    StaticFiles(directory="static"),
    name="static",
)


@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=f"{app.title} - Swagger UI",
        swagger_js_url="/static/swagger-ui/swagger-ui-bundle.js",
        swagger_css_url="/static/swagger-ui/swagger-ui.css",
        swagger_favicon_url="/static/swagger-ui/favicon.png",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_ui_parameters={
            "docExpansion": "none",
            "defaultModelsExpandDepth": -1,
        },
    )


@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
async def swagger_ui_redirect():
    return get_swagger_ui_oauth2_redirect_html()


@app.get("/test")
async def test():
    return {"message": "ok"}

核心配置是:

python 复制代码
swagger_js_url="/static/swagger-ui/swagger-ui-bundle.js"
swagger_css_url="/static/swagger-ui/swagger-ui.css"

这两个参数表示 /docs 页面不再从外部 CDN 加载 Swagger UI 资源,而是从当前 FastAPI 服务的 /static 路径加载。


七、为什么要设置 docs_url=None

代码中使用了:

python 复制代码
app = FastAPI(
    docs_url=None,
    redoc_url=None,
)

这是为了关闭 FastAPI 默认生成的 /docs 页面。

然后我们自己重新注册一个 /docs 路由:

python 复制代码
@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    ...

这样可以完全控制 Swagger UI 的 JS、CSS、favicon、页面标题等资源地址。


八、验证是否配置成功

启动服务:

bash 复制代码
uvicorn main:app --host 0.0.0.0 --port 8000

访问:

text 复制代码
http://127.0.0.1:8000/docs

然后再访问静态资源地址:

text 复制代码
http://127.0.0.1:8000/static/swagger-ui/swagger-ui-bundle.js
http://127.0.0.1:8000/static/swagger-ui/swagger-ui.css

如果这两个地址都能正常返回内容,说明静态资源配置成功。

再打开浏览器开发者工具,查看 Network。

正常情况下应该能看到:

text 复制代码
swagger-ui-bundle.js  200
swagger-ui.css        200
/openapi.json         200

如果都是 200,/docs 页面一般就可以正常显示。


九、如果仍然加载慢,可以继续优化

如果本地化静态资源后,/docs 仍然加载慢,可以继续检查以下几点。

1. 开启 gzip 压缩

swagger-ui-bundle.js 文件体积较大,建议在 Nginx 中开启 gzip 压缩。

示例:

nginx 复制代码
gzip on;
gzip_types text/plain text/css application/javascript application/json;
gzip_min_length 1k;

如果没有使用 Nginx,也可以在 FastAPI 中使用 GZipMiddleware

python 复制代码
from fastapi.middleware.gzip import GZipMiddleware

app.add_middleware(GZipMiddleware, minimum_size=1000)

2. 开启静态资源缓存

如果每次打开 /docs 都重新下载 JS 文件,也会影响速度。

可以在 Nginx 中给静态资源设置缓存:

nginx 复制代码
location /static/ {
    expires 7d;
    add_header Cache-Control "public";
}

这样浏览器第二次打开 /docs 时,就可以直接使用缓存资源。


3. 检查 /openapi.json 是否过大

有时候白屏不是 JS 加载慢,而是 /openapi.json 太大。

如果接口数量很多、Pydantic 模型很复杂,Swagger UI 在渲染大量接口时也会变慢。

可以访问:

text 复制代码
http://127.0.0.1:8000/openapi.json

检查这个文件是否非常大。

如果 /openapi.json 很大,可以考虑:

  1. 减少无用接口暴露;
  2. 内部接口设置 include_in_schema=False
  3. 拆分多个 FastAPI 应用;
  4. 不把所有后台任务接口都放到同一个 Swagger 文档中;
  5. 关闭默认展开模型。

例如:

python 复制代码
@app.get("/internal/task", include_in_schema=False)
async def internal_task():
    return {"status": "ok"}

4. 不要把静态目录路径写错

如果代码中写的是:

python 复制代码
StaticFiles(directory="static")

那么项目运行目录下必须存在:

text 复制代码
static/

如果你是在其他目录执行 uvicorn,可能会因为相对路径不同导致找不到静态文件。

更稳妥的写法是使用绝对路径:

python 复制代码
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent

app.mount(
    "/static",
    StaticFiles(directory=BASE_DIR / "static"),
    name="static",
)

这样不管从哪个目录启动项目,都能正确找到静态资源。


十、完整推荐版代码

python 复制代码
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.openapi.docs import (
    get_swagger_ui_html,
    get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles


BASE_DIR = Path(__file__).resolve().parent

app = FastAPI(
    title="My API",
    docs_url=None,
    redoc_url=None,
)

app.add_middleware(GZipMiddleware, minimum_size=1000)

app.mount(
    "/static",
    StaticFiles(directory=BASE_DIR / "static"),
    name="static",
)


@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title=f"{app.title} - Swagger UI",
        swagger_js_url="/static/swagger-ui/swagger-ui-bundle.js",
        swagger_css_url="/static/swagger-ui/swagger-ui.css",
        swagger_favicon_url="/static/swagger-ui/favicon.png",
        oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
        swagger_ui_parameters={
            "docExpansion": "none",
            "defaultModelsExpandDepth": -1,
            "displayRequestDuration": True,
        },
    )


@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
async def swagger_ui_redirect():
    return get_swagger_ui_oauth2_redirect_html()


@app.get("/test")
async def test():
    return {"message": "ok"}

十一、最终效果

修改后,/docs 页面加载流程变成:

text 复制代码
浏览器访问 /docs
        ↓
FastAPI 返回 Swagger UI HTML
        ↓
浏览器从 /static/swagger-ui/swagger-ui-bundle.js 加载 JS
        ↓
浏览器从 /static/swagger-ui/swagger-ui.css 加载 CSS
        ↓
浏览器请求 /openapi.json
        ↓
Swagger UI 正常渲染接口文档

这样就避免了外部 CDN 访问慢、网络阻断、JS 文件超时等问题。


十二、总结

FastAPI /docs 页面白屏或加载时间过长,大多数情况下不是接口服务本身的问题,而是 Swagger UI 所依赖的前端资源加载失败或加载过慢。

推荐解决方式是:

text 复制代码
将 swagger-ui-bundle.js 和 swagger-ui.css 下载到本地
使用 StaticFiles 挂载静态目录
通过 get_swagger_ui_html 自定义 swagger_js_url 和 swagger_css_url

核心代码如下:

python 复制代码
def swagger_monkey_patch(*args, **kwargs):
    """
    Wrap the function which is generating the HTML for the /docs endpoint and overwrite the default values for the swagger js and css.
    """
    return get_swagger_ui_html(
        *args, **kwargs,
        swagger_js_url="/static/swagger-ui/swagger-ui-bundle.js",
        swagger_css_url="/static/swagger-ui/swagger-ui.css",
    )

这种方式适合:

  1. 内网部署;
  2. 服务器无法访问外网;
  3. CDN 访问慢;
  4. /docs 页面白屏;
  5. Swagger UI JS 文件 pending;
  6. 线上接口文档打开很慢。

配置完成后,再配合 gzip 压缩和静态资源缓存,可以明显提升 FastAPI 文档页面的打开速度。