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

Nuxt3 SSG部署Azure Web App后Server API路由返回404排查

问题:Nuxt3 SSG部署到Azure Web App后Server API路由返回404

我通过DevOps流水线将Nuxt3 SSG应用部署到Azure Web App,部署过程无异常,但访问站点时所有Server API路由都返回404错误。排查发现server API路由没有被打包到.output文件夹中,这些404错误来自$fetch调用API路由——应用重定向到/login后,第一个错误出现在login布局的onMounted阶段。


相关配置文件

layouts/login.vue

<script setup>
onMounted(async () => {
   await $fetch(`/api/startup`, {
       method: 'post'
   });
});
</script>

nuxt.config.ts

import { copy } from '@nuxt/utils';

export default defineNuxtConfig({
    devtools: {
        enabled: process.env.NODE_ENV !== 'production',
        timeline: {
            enabled: process.env.NODE_ENV !== 'production'
        }
    },
    ssr: false,
    modules: [
        [
            '@pinia/nuxt',
            {
                autoImports: ['defineStore', 'acceptHMRUpdate', 'storeToRefs']
            }
        ],
        '@nuxtjs/tailwindcss',
        '@nuxtjs/color-mode'
    ],
    css: [
        '@/assets/css/main.css'
    ],
    hooks: {
        generate: {
            after: async () => {
                await copy('server', '.output/public/server'); // 尝试复制server文件夹到.output
            }
        },
    },
    app: {
        baseURL: '/',
        buildAssetsDir: '.output'
    },
    runtimeConfig: {
       // 省略配置
    }
})

azure-pipelines.yaml

trigger:
- sandbox

pool:
  vmImage: ubuntu-latest

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '18.x'
  displayName: '安装Node.js'

- script: |
    npm install
  displayName: '安装依赖'

- script: |
    npm run generate
  displayName: '生成静态站点'

- task: AzureWebApp@1
  inputs:
    azureSubscription: 'secret-string'
    appType: webApp
    appName: 'secret-app-name'
    slotName: 'development'
    package: '.output/public'
    deploymentMethod: 'runFromPackage'
  displayName: '部署到Azure Web App'

web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <defaultDocument>
      <files>
        <clear />
        <add value="public/index.html" />
      </files>
    </defaultDocument>

    <directoryBrowse enabled="false" />

    <rewrite>
      <rules>
        <rule name="Handle History Mode and custom routes" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>

    <httpErrors>
      <remove statusCode="404" subStatusCode="-1" />
      <error statusCode="404" path="/404.html" responseMode="ExecuteURL" />
    </httpErrors>
  </system.webServer>
</configuration>

部署后的目录结构

流水线运行后,Azure Web App的wwwroot目录结构如下:

/.output
/login
200.html
400.html
favicon.ico
index.html

问题分析与解决步骤

核心问题

  1. SSG模式不支持Server API路由:Nuxt3的ssr: false + npm run generate生成的是纯静态站点,Server API路由(server/目录下的文件)属于服务端运行时代码,不会被打包到静态输出中——静态站点根本无法运行服务端API。
  2. 复制server文件夹无效:即使通过generate:after钩子把server复制到.output/public,静态Web App也无法解析这些Node.js代码,因为Azure Web App的静态站点模式不会启动Node.js服务。
  3. 路由重写规则覆盖API请求:web.config中的重写规则会把所有非文件/非目录的请求重写到/,导致/api/*请求被强制跳转到首页,最终返回404。

解决方法

方案1:切换到Nuxt3服务端部署模式(推荐)

如果必须保留Server API路由,放弃SSG模式,改用服务端部署:

  • 修改nuxt.config.ts:可以保留ssr: true(默认值可直接省略);如果需要客户端渲染但保留服务端API,保持ssr: false但将构建命令从generate改为build。
  • 调整流水线脚本:把npm run generate替换为npm run build。
  • 修改Azure部署任务的package路径:从.output/public改为.output
    package: '.output'
    
  • 确认Azure Web App配置:在Azure门户的应用服务配置中,设置运行时栈为Node.js。

方案2:将API逻辑迁移到客户端

如果API逻辑不需要服务端执行,把server/api下的代码迁移到客户端,或者用静态JSON文件作为数据源,彻底避免依赖服务端API。

方案3:单独部署API到Azure Functions

把Server API路由迁移到Azure Functions,客户端$fetch直接调用Functions的公开URL,实现前后端分离部署。

额外调整:修复web.config重写规则

如果选择方案1,需要修改web.config,确保API请求不被重写规则拦截:

<rewrite>
  <rules>
    <!-- 优先处理API请求,不触发SPA重写 -->
    <rule name="API Routes" stopProcessing="true">
      <match url="^api/(.*)" />
      <action type="Rewrite" url="api/{R:1}" />
    </rule>
    <!-- 处理SPA历史模式路由 -->
    <rule name="Handle History Mode" stopProcessing="true">
      <match url=".*" />
      <conditions logicalGrouping="MatchAll">
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
      </conditions>
      <action type="Rewrite" url="/" />
    </rule>
  </rules>
</rewrite>

内容的提问来源于stack exchange,提问作者mojojojo8

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 05:07:37