Outline
On Azure, you can host a static web multiple ways
- As separate App Service
- Via Blob Storage and CDN
- As a part of App Service (that hosts, e.g., an API)
Hosting As a Part of App Service
You you want to host the site in some existing App Service that already hosts some app, e.g., Web Api, it is a very simple process – just copy the built web into wwwroot folder. There is only one problem – proper routing. Because now there are basically two sources that are provided:
the simplest way how to distinguish between Api and Web App is to use different ports. But it is not possible in our case because App Service supports only port 80/443. So it must be done in a different way.
Rewrite Rules
In out case we will use rewrite rules defined in immortal web.config file. Basically, we have to distinguish between following resources:
- Api routes (third negation in wwwroot-spa-routing-to-index rule)
- Web App root file, e.g., index.html (rules wwwroot-static-files and wwwroot-spa-routing-to-index)
- Static files, e.g., images, fonts, styles, etc. (content of element handlers)
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.webServer xdt:Transform="Insert">
<rewrite>
<rules>
<rule name="wwwroot-static-files" stopProcessing="true">
<!-- Following extensions should be defined in <handlers /> section -->
<match url="([\S]+[.](html|htm|svg|js|css|png|gif|jpg|jpeg|woff|woff2|eot|ttf|map))" />
<action type="Rewrite" url="wwwroot/{R:1}" />
</rule>
<rule name="wwwroot-index" stopProcessing="true">
<match url="^$" />
<action type="Rewrite" url="wwwroot/index.html" />
</rule>
<!--
Note: root path must be set to "/" ,i.e., <base href="/" />
otherwise relative paths will not work
-->
<rule name="wwwroot-spa-routing-to-index" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{REQUEST_URI}" pattern="api/" negate="true" />
</conditions>
<action type="Rewrite" url="wwwroot/index.html" />
</rule>
</rules>
</rewrite>
<handlers>
<add name="StaticFileModuleHtml" path="*.htm*" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleSvg" path="*.svg" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleJs" path="*.js" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleCss" path="*.css" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleJpeg" path="*.jpeg" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleJpg" path="*.jpg" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModulePng" path="*.png" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleGif" path="*.gif" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleWoff" path="*.woff" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleWoff2" path="*.woff2" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleEot" path="*.eot" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleTtf" path="*.ttf" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="StaticFileModuleMap" path="*.map" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" />
</system.webServer>
</configuration>
Thats all. Routes matching api/ pattern will be routed to your Web Api. All other routes except static files will be routed to the index.html, where the SPA’s routing will do its work.
Conclusion
There are many methods how to publish SPA web site in Azure. If you want to host together with your Web Api in the same App Service, all you need is a proper web.config setup.