Optimizing static websites hosted on IIS
So you are building a website using static .html files instead of any server side technologies such as ASP.NET. That’s cool for various reasons, but my favorite is that it allows any developer on any platform to easily contribute on GitHub. No server-side components needed. Great!
You’re almost done and decide to run performance analytics tool such as Google Page Speed on your site. Now the problems begin. Here’s some of the items that you are told to optimize:
- Minify HTML
- Set far-future expiration dates on static resources (JS, CSS, images etc.)
- Use cookieless domains for static files
- Use a CDN
You could set up build processes using Grunt to do all of this work, but it is not that simple to do – especially after you already built your website. Most of these tools require you to setup your project in a specific way from the beginning.
When you think about it, none of the above mentioned performance issues are relevant on a developer machine, they are only applicable to the live running production website. So if we could let the production server do some tricks for us to make all of this easier and without us having to modify our source code, that would be great.
StaticWebHelper
While building SchemaStore.org I encountered exactly these issues and decided to create a generic and reusable solution. My idea was to let IIS handle the issues while the website could still run statically without IIS at all on a development machine.
The StaticWebHelper NuGet package does exactly that. Here’s what it does:
- Minifies any .html file at runtime and output caches
- Fingerprints references to static resources
- Creates a URL rewrite rule for handling the fingerprints
- Set’s far future expiration dates in the web.config
- Has support for CDNs using an appSetting
Fingerprinting is a browser cache busting technique for changing the URL to references files, so the browsers will load any changes while still featuring far-future expiration dates. Read more about fingerprinting.
#1 and #2 happens at runtime, but only once.
<handlers> <add name="FingerPrint" verb="GET" path="*.html" type="StaticWebHelper.FingerPrintHandler" /> </handlers>It output caches the results so that no additional files are being created on disk and you get performance similar to static file serving. Any time a referenced JS, CSS or image file is updated on disk, it generates new fingerprints automatically. It also handles conditional GET requests (status 304).
#3, #4 and #5 are all handled in the web.config.
<add key="cdnPath" value="http://schemastore.org.m82.be/" /> <add key="minify" value="true" />
I use a custom reverse proxy CDN with nodes in both Europe and North America for serving static files cookieless. If you don’t need a CDN, it is still a good idea to use a different subdomain to handle static resources such as s.mydomain.com. StaticWebHelper supports both scenarios equally and it’s easy to setup in web.config.
For fingerprinting to work, it adds a URL rewrite rule in web.config.
<rule name="FingerPrint" stopProcessing="true"> <match url="(.+)(\.[0-9]{18})\.([a-z]{2,4})$" /> <action type="Rewrite" url="{R:1}.{R:3}" /> </rule>
To see this in action, check out the source code of SchemaStore.org on GitHub. Especially, take a look in the web.config file.
Azure Site Extensions
If your website is hosted on Azure, then it’s really easy to let an automated Site Extension do further optimizations such as image optimization and JS/CSS minification. Read more about that here.