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

将Angular 18 SSR应用部署到IIS时遇404错误求助

Angular 18 SSR(.NET Core模板)部署IIS后页面404问题

我正在尝试将基于.NET Core模板搭建的Angular 18 SSR应用部署到本地IIS。通过Visual Studio发布并将文件夹复制到IIS后,API可正常运行,但Angular页面无法加载,出现404错误。我下载了一个Angular SSR模板尝试发布,仍遇到相同问题。

以下是我的angular.json配置:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "test.web.application.client": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "standalone": false
        },
        "@schematics/angular:directive": {
          "standalone": false
        },
        "@schematics/angular:pipe": {
          "standalone": false
        }
      },
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:application",
          "options": {
            "outputPath": {
              "base": "dist/test.web.application.client",
              "browser": "browser"
            },
            "index": "src/index.html",
            "browser": "src/main.ts",
            "polyfills": [
              "zone.js"
            ],
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/assets",
              "src/sitemap.xml",
              "src/sitemaptest.xml",
              "src/sitemap_ca.xml",
              "src/sitemap_es.xml",
              "src/sitemap_en.xml",
              "src/sitemap_fr.xml",
              "src/sitemap_de.xml",
              "src/sitemap_pt.xml",
              "src/sitemap_it.xml",
              "src/robots.txt",
              "src/robots_notracking.txt",
              "src/robots_tracking.txt"
            ],
            "allowedCommonJsDependencies": [
              "moment",
              "jquery",
              "fast-deep-equal",
              "rgbcolor",
              "raf",
              "guid-typescript",
              "core-js/modules/web.dom-collections.iterator.js",
              "core-js/modules/es.string.trim.js",
              "core-js/modules/es.string.starts-with.js",
              "core-js/modules/es.string.split.js",
              "core-js/modules/es.string.replace.js",
              "core-js/modules/es.string.match.js",
              "core-js/modules/es.string.includes.js",
              "core-js/modules/es.string.ends-with.js",
              "core-js/modules/es.regexp.to-string.js",
              "core-js/modules/es.promise.js",
              "core-js/modules/es.array.reverse.js",
              "core-js/modules/es.array.reduce.js",
              "core-js/modules/es.array.iterator.js",
              "core-js/modules/es.array.index-of.js",
              "file-saver",
              "js-sha256"
            ],
            "styles": [
              "src/assets/ion.rangeSlider.min.css",
              "./node_modules/@angular/cdk/_index.scss"
            ],
            "scripts": [
              "./node_modules/jquery/dist/jquery.min.js",
              "src/assets/js/moment.min.js",
              "src/assets/js/slick.min.js",
              "src/assets/js/jquery.countdown.min.js",
              "src/assets/js/ion.rangeSlider.min.js"
            ],
            "server": "src/main.server.ts",
            "prerender": false,
            "ssr": {
              "entry": "server.ts"
            }
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kb",
                  "maximumError": "10mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "2kb",
                  "maximumError": "4kb"
                }
              ],
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "index": {
                "input": "src/index.prod.html",
                "output": "index.html"
              },
              "outputHashing": "all"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "test.web.application.client:build:production"
            },
            "development": {
              "buildTarget": "test.web.application.client:build:development"
            }
          },
          "defaultConfiguration": "development",
          "options": {
            "proxyConfig": "src/proxy.conf.js"
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "buildTarget": "test.web.application.client:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": [
              "zone.js",
              "zone.js/testing"
            ],
            "tsConfig": "tsconfig.spec.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": [],
            "karmaConfig": "karma.conf.js"
          }
        }
      }
    }
  },
  "cli": {
    "analytics": false
  }
}

解决步骤

1. 配置IIS URL重写规则

SSR应用需要将所有非静态文件请求转发到.NET Core应用处理,否则IIS会直接查找文件导致404:

  • 先安装IIS的URL重写模块(通过Web平台安装器获取)
  • 在站点根目录的web.config中添加以下规则:
<rewrite>
  <rules>
    <rule name="Angular SSR" 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>

2. 检查发布目录结构

确认Visual Studio发布后,dist/test.web.application.client/browser和server文件夹已完整复制到IIS站点目录,且.NET Core应用有权限访问这些文件。

3. 验证.NET Core SSR服务配置

在Program.cs中确保SpaStaticFiles和SSR的路径配置正确,对应angular.json中的outputPath:

builder.Services.AddSpaStaticFiles(configuration =>
{
    configuration.RootPath = "dist/test.web.application.client/browser";
});

// 其他中间件配置...

app.UseSpaStaticFiles();

app.UseSpa(spa =>
{
    spa.Options.SourcePath = "ClientApp";

    if (!builder.Environment.IsDevelopment())
    {
        spa.UseSpaPrerendering(options =>
        {
            options.BootModulePath = $"{spa.Options.SourcePath}/dist/test.web.application.client/server/main.js";
            options.ExcludeUrls = new[] { "/sockjs-node" };
        });
    }
});

4. 调整IIS应用池设置

  • 将应用池的**.NET CLR版本**设置为无托管代码
  • 确保应用池标识(如ApplicationPoolIdentity)对站点目录有读取权限

5. 手动验证SSR构建完整性

运行以下命令手动构建SSR产物,检查dist目录是否生成完整的browser和server文件夹:

ng build --configuration production
ng run test.web.application.client:server:production

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 23:19:54