Hosting Vue JS SPA build on IIS

05/04/2019 6:44:44 CH




If you use vue-router in your Vue JS SPA web application, your app router will route navigational paths to virtual folders and files. Note that there are no physical asset for these URLs, thus, if you were to host your web app directly under IIS, you will get 404 errors if you navigate directly to these URLs or when you refresh/reload the page.



Option 1:

If you use vue-router in your Vue JS SPA web application, your app router will route navigational paths to virtual folders and files. Note that there are no physical asset for these URLs, thus, if you were to host your web app directly under IIS, you will get 404 errors if you navigate directly to these URLs or when you refresh/reload the page. You will need to set URL rewrite rules for IIS to manage these URLs properly. Also, you will need to setup IIS as such:

1. Build your Vue JS SPA app(s) using 'npm run build'

2. In IIS, create new website pointing to the '/dist' folder of your web app

3. Create a new Web.config file on the root folder with the following lines of codes:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Handle History Mode and custom 404/500" 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="index.html" />
        </rule>
      </rules>
    </rewrite>
      <httpErrors>     
          <remove statusCode="404" subStatusCode="-1" />                
          <remove statusCode="500" subStatusCode="-1" />
          <error statusCode="404" path="/survey/notfound" responseMode="ExecuteURL" />                
          <error statusCode="500" path="/survey/error" responseMode="ExecuteURL" />
      </httpErrors>
      <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

4. In IIS, go to Application Pools > the new website > Advanced Settings > .NET CLR Version, choose 'No Managed Code'

5. At this point, you would need to install IIS URL Rewrite Extension, so, go to https://www.iis.net/downloads/microsoft/url-rewrite, download and install (with MS Web Platform Installer).

6. Restart IIS

At this point you should be able to navigate to your new website URL and your Vue JS SPA routes should work with refresh and reloads. Do leave message down below if you need to discuss anything. Hope this helps :)

 

Option 2: HTML5 History Mode

The default mode for vue-router is hash mode - it uses the URL hash to simulate a full URL so that the page won't be reloaded when the URL changes.

To get rid of the hash, we can use the router's history mode, which leverages the history.pushStateAPI to achieve URL navigation without a page reload:

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

When using history mode, the URL will look "normal," e.g. http://oursite.com/user/id. Beautiful!

Here comes a problem, though: Since our app is a single page client side app, without a proper server configuration, the users will get a 404 error if they access http://oursite.com/user/id directly in their browser. Now that's ugly.

Not to worry: To fix the issue, all you need to do is add a simple catch-all fallback route to your server. If the URL doesn't match any static assets, it should serve the same index.html page that your app lives in. Beautiful, again!

#Example Server Configurations

#Apache

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

Instead of mod_rewrite, you could also use FallbackResource.

#nginx

location / {
  try_files $uri $uri/ /index.html;
}

#Native Node.js

const http = require('http')
const fs = require('fs')
const httpPort = 80

http.createServer((req, res) => {
  fs.readFile('index.htm', 'utf-8', (err, content) => {
    if (err) {
      console.log('We cannot open "index.htm" file.')
    }

    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8'
    })

    res.end(content)
  })
}).listen(httpPort, () => {
  console.log('Server listening on: http://localhost:%s', httpPort)
})

#Express with Node.js

For Node.js/Express, consider using connect-history-api-fallback middleware.

#Internet Information Services (IIS)

  1. Install IIS UrlRewrite
  2. Create a web.config file in the root directory of your site with the following:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Handle History Mode and custom 404/500" 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>
  </system.webServer>
</configuration>

#Caddy

rewrite {
    regexp .*
    to {path} /
}

#Firebase hosting

Add this to your firebase.json:

{
  "hosting": {
    "public": "dist",
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

#Caveat

There is a caveat to this: Your server will no longer report 404 errors as all not-found paths now serve up your index.html file. To get around the issue, you should implement a catch-all route within your Vue app to show a 404 page:

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})

Alternatively, if you are using a Node.js server, you can implement the fallback by using the router on the server side to match the incoming URL and respond with 404 if no route is matched. Check out the Vue server side rendering documentation for more information.