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

Azure静态Web Apps全栈应用部署构建问题求助

问题:Azure静态Web Apps部署后后端无法启动

我为客户开发了一款目录网站,技术栈如下:

  • 前端:Vue.js + Vite
  • 后端:Express/Node.js
  • 数据库:Azure SQL

本地运行完全正常,但通过GitHub Actions自动部署到Azure静态Web Apps后,前端加载正常,后端却无法启动,没有任何服务器/API日志,请求所有端点都返回连接拒绝。

已尝试的解决方案:

  • 修改api_start_command用Node启动服务器,但服务器启动后无法关闭,导致部署停滞
  • 尝试通过Vite启动,发现构建后服务器/API文件缺失
  • 查阅微软官方文档调整构建配置,也试过Azure Web App,结果一致

目前怀疑是Vite配置或架构设置问题,想请教:是否需要单独部署后端?是否需要独立仓库?

以下是相关代码:

Vite.config.js

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import copy from 'rollup-plugin-copy';
export default defineConfig({
plugins: [
vue(),
],
build: {
rollupOptions: {
plugins: [
copy({
targets: [
{ src: 'server.js', dest: 'dist' },
{ src: 'api.js', dest: 'dist' },
],
hook: 'writeBundle'
}),
]
}
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

GitHub Actions 工作流配置

name: Deploy web app to Azure Static Web Apps
env:
APP_LOCATION: "/evo" # location of your client code
API_LOCATION: "/evo" # location of your api source code - optional
OUTPUT_LOCATION: "dist" # location of client code build output
DB_USER: ${{ secrets.DB_USER }}
DB_PASS: ${{ secrets.DB_PASS }}
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main
permissions:
issues: write
contents: read
pull-requests: write
jobs:
build_and_deploy:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Build And Deploy
uses: Azure/static-web-apps-deploy@1a947af9992250f3bc2e68ad0754c0b0c11566c9
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_PEBBLE_0E2297810 }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: ${{ env.APP_LOCATION }}
api_location: ${{ env.API_LOCATION }}
output_location: ${{ env.OUTPUT_LOCATION }}

close_pull_request:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
name: Close Pull Request
steps:
- name: Close Pull Request
uses: Azure/static-web-apps-deploy@1a947af9992250f3bc2e68ad0754c0b0c11566c9
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_PEBBLE_0E2297810 }}
action: "close"

api.js

const express = require('express');
const app = express();
const cors = require('cors');
var sql = require('mssql'); // MS Sql Server client
const port = process.env.PORT || 1433;
const pool = require('./server'); // Import the database connection pool

app.use(express.json());

// Enable CORS for all routes
app.use(cors());

// Get All Categories
app.get('/api/data/categories', async (req, res) => {
  try {
    const request = new sql.Request(pool);
    const result = await request.query('SELECT * FROM categories2');
    res.json(result.recordset);
  } catch (err) {
    console.error('Error executing SQL query:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Get Parent Categories
app.get('/api/data/categories/parents', async (req, res) => {
  try {
    const request = new sql.Request(pool);
    const result = await request.query('SELECT * FROM categories2 WHERE parentCategory = 1');
    res.json(result.recordset);
  } catch (err) {
    console.error('Error executing SQL query:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Get Subcategories by Category
app.get('/api/data/categories/:parent', async (req, res) => {
  const { parent } = req.params; // Retrieve the parent parameter from the URL
  try {
    const request = new sql.Request(pool);
    const query = `SELECT * FROM categories2 WHERE parentId = @parent`; // Use a parameterized query to filter by parent
    request.input('parent', sql.NVarChar, parent);
    const result = await request.query(query);
    res.json(result.recordset);
  } catch (err) {
    console.error('Error executing SQL query:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Get All Products
app.get(`/api/data/products`, async (req, res) => {
  try {
    const request = new sql.Request(pool);
    const result = await request.query('SELECT * FROM products');
    res.json(result.recordset);
  } catch (err) {
    console.error('Error executing SQL query:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Get Products by Category
app.get('/api/data/products/:category', async (req, res) => {
  const { category } = req.params; // Retrieve the category parameter from the URL
  try {
    const request = new sql.Request(pool);
    const query = `SELECT * FROM products WHERE Category_ID = @category`; // Use a parameterized query to filter by category
    request.input('category', sql.NVarChar, category);
    const result = await request.query(query);
    res.json(result.recordset);
  } catch (err) {
    console.error('Error executing SQL query:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// Get Product by Part Number
app.get('/api/data/product/:partNum', async (req, res) => {
  const { partNum } = req.params; // Retrieve the category parameter from the URL
  try {
    const request = new sql.Request(pool);
    console.log(request);
    const query = `SELECT * FROM products WHERE Part_Number = @partNum`; // Use a parameterized query to filter by category
    console.log(query);
    request.input('partNum', sql.NVarChar, partNum);
    const result = await request.query(query);
    if (result.recordset.length > 0) {
      res.json(result.recordset); // Return the results if any are found
    } else {
      res.status(404).json({ error: 'Product not found' }); // Return 404 if no product is found
    }  
  } catch (err) {
    console.error('Error executing SQL query:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

server.js

var express = require('express'); // Web Framework
var app = express();
var sql = require('mssql'); // MS Sql Server client
var test = require('dotenv').config();

var config = {
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  server: process.env.SERVER_NAME,
  database: 'EvoDB'
}

// Create a pool of database connections
const pool = new sql.ConnectionPool(config);

// Connect to the database
pool.connect()
  .then(() => console.log('Connected to SQL Server'))
  .catch((err) => console.error('Database connection error:', err));

module.exports = pool;

解决方案

1. 核心问题:Azure静态Web Apps的API部署规范

Azure静态Web Apps要求后端API必须放在单独的api目录下,且目录内需要包含package.json定义依赖和启动脚本,不能和前端代码混在同一目录。你的配置中API_LOCATION: "/evo"指向了前端目录,导致部署流程无法正确识别和构建后端代码。

2. 具体修复步骤

步骤1:调整项目结构

把后端代码(api.js、server.js)移动到项目根目录下的api文件夹中,并在该目录创建package.json:

项目根目录
├── evo/                # 前端Vue代码
│   ├── src/
│   ├── vite.config.js
│   └── package.json
└── api/                # 后端Express代码
    ├── api.js
    ├── server.js
    └── package.json

步骤2:配置后端package.json

在api/package.json中定义启动脚本和依赖:

{
  "name": "catalog-api",
  "version": "1.0.0",
  "main": "api.js",
  "scripts": {
    "start": "node api.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "cors": "^2.8.5",
    "mssql": "^10.0.2",
    "dotenv": "^16.3.1"
  }
}

步骤3:修改GitHub Actions配置

更新环境变量中的API_LOCATION为后端目录:

env:
  APP_LOCATION: "/evo"
  API_LOCATION: "/api"  # 修改为后端目录
  OUTPUT_LOCATION: "dist"

步骤4:移除Vite中的copy插件

Azure静态Web Apps会单独处理API目录的部署,无需将后端文件复制到前端dist目录,删除Vite.config中的rollup-plugin-copy相关配置:

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

步骤5:配置后端环境变量

后端的环境变量需要在Azure门户的配置 > 应用程序设置中添加,而非GitHub Actions:
登录Azure门户,找到你的静态Web Apps资源,添加以下应用设置:

  • DB_USER:Azure SQL用户名
  • DB_PASS:Azure SQL密码
  • SERVER_NAME:Azure SQL服务器名称(格式:xxx.database.windows.net)

3. 是否需要单独部署后端/独立仓库?

  • 不需要独立仓库:Azure静态Web Apps支持单仓库中前端+后端的部署,只要目录结构符合规范即可。
  • 若后续后端复杂度提升,或需要独立的扩缩容、部署周期,可以考虑单独部署到Azure App Service,此时需要独立仓库或单独的部署流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 01:35:56