Gatsby site with Azure Web App and Application Insights
02 August, 2020 - 14 min read
As it is for my other blog posts, this post is also a story about a few things I've learned while working my most recent project, this web site!
If you check a few posts in this web site, you may have noticed that I'm having a hard time keeping backups of my blog posts. As time passes, I tend to loose backups of the articles when I had to take servers offline. I've been thinking about this issue for some time and been thinking what I can do to keep everything some place secure, and also in a way which I can easily maintain. In the same time, GitHub Markdown Language
been growing in me simply because how easy it is to write technical posts using it. With the combination of these two remarks, I decided to find a platform which is easy to manage and also supports markdown language for posts.
After looking around for a blogging engine which supports my requirements, I came across gatsby
, I've seen people talk about this in social media, but I never had a chance to try it out. After digging more into how it works, I decided that it is the perfect solution for me. Easy to manage, markdown supported, static site generator. Then the theme, it was an easy pick, gatsby-starter-julia theme was simple, had the look and feel I'd love to have in my blog, it was just a matter of creating the new site using theme and start working on it. Steps to create the site was really easy to follow, articles from gatsby
site and details in gatsby-starter-julia was simple enough that I was able to get the base site up and running locally within few a minutes of time. After that, I spent a few hours to make it look like how I wanted it to be, and migrating old articles from WordPress and web.archive.org.
Being the easy part of the process being done, here are the things I did after to make it available for you. 😊
web.config
and security headers
Since I'm using Azure Web App to host the gatsby
web site, in order to add different kinds of server configurations, I have to use web.config
file. A good start point to get this working is to read this article from gatsby
, Deploying to Microsoft Internet Information Server (IIS).
After going through this article, I came up with below web.config
file (which resides on /static
folder),
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<location path="static">
<system.webServer>
<httpProtocol>
<customHeaders>
<remove name="Cache-Control" />
<remove name="Strict-Transport-Security" />
<add name="Cache-Control" value="public, max-age=31536000, immutable" />
<add name="Strict-Transport-Security" value="max-age=31536000" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
<system.webServer>
<httpProtocol>
<customHeaders>
<remove name="Cache-Control" />
<remove name="Strict-Transport-Security" />
<remove name="Content-Security-Policy" />
<remove name="X-XSS-Protection" />
<remove name="X-Frame-Options" />
<remove name="X-Content-Type-Options" />
<add name="Cache-Control" value="public, max-age=0, must-revalidate" />
<add name="Strict-Transport-Security" value="max-age=0" />
<add name="Content-Security-Policy" value="default-src 'self' 'unsafe-inline'" />
<add name="X-XSS-Protection" value="1; mode=block" />
<add name="X-Frame-Options" value="SAMEORIGIN" />
<add name="X-Content-Type-Options" value="nosniff" />
</customHeaders>
</httpProtocol>
<staticContent>
<mimeMap fileExtension=".webmanifest" mimeType="application/manifest+json" />
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
<mimeMap fileExtension=".json" mimeType="application/json" />
</staticContent>
<rewrite>
<outboundRules>
<rule name="AdjustCacheForCachePermanentlyFiles" preCondition="IsCachePermanentlyFile">
<match serverVariable="RESPONSE_Cache-Control" pattern=".*" />
<action type="Rewrite" value="public, max-age=31536000, immutable" />
</rule>
<rule name="AdjustCacheForCachePermanentlyFilesSTS" preCondition="IsCachePermanentlyFile" stopProcessing="true">
<match serverVariable="RESPONSE_Strict-Transport-Security" pattern=".*" />
<action type="Rewrite" value="max-age=31536000" />
</rule>
<preConditions>
<preCondition name="IsCachePermanentlyFile">
<add input="{REQUEST_FILENAME}" pattern="((.*\.js)|(.*\.css)|(.*\.png)|(.*\.jpg)|(.*\.ico)|(.*\.woff)|(.*\.woff2))$" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>
Here are few points about the above configuration file,
-
Please read about each response headers from below articles if you already do not know about them,
Cache-Control
: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control, good read: https://stackoverflow.com/questions/1046966/whats-the-difference-between-cache-control-max-age-0-and-no-cacheStrict-Transport-Security
: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-SecurityContent-Security-Policy
: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSPX-XSS-Protection
: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-ProtectionX-Frame-Options
: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-OptionsX-Content-Type-Options
: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
Line 3-14
: Content in/static
folder are static content for the site. These files does not have to cached in the browser. We make it so that it overrides default values forCache-Control
andStrict-Transport-Security
here.Line 16-31
: Default content security headers to be used for files.Line 32-37
: I have addedmimeMap
forwoff
,woff2
andjson
files which used by the site.Line 39-53
: These are special overrides forCache-Control
andStrict-Transport-Security
depends on the file extension. This makes different types of static files in the site cached in the browser so the browser doesn't have to load them each time.
CI/CD, deploying my blog via GitHub actions
I'm not going to detail things I did here simply because I followed steps from an awesome article by Jen Looper.
Read: Deploy your Website on Azure with GitHub Actions like a Hipster
My final yaml
looked like this,
name: bitsnorbytes.com build and deploy to Azure Web App
on:
[push]
env:
AZURE_WEBAPP_NAME: bitsnorbytes
AZURE_WEBAPP_PACKAGE_PATH: './public'
NODE_VERSION: '10.x'
jobs:
build-and-deploy:
name: Build and Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2.3.1
with:
persist-credentials: false
- name: Install npm ${{ env.NODE_VERSION }} 🔧
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install packages 🔧
run: |
npm install
- name: Build project 🔧
run: |
npm run-script build
- name: Deploy to Azure WebApp 🚀
uses: azure/webapps-deploy@v1
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
SSL all-the-way, LetsEncrypt for Azure Web Apps
Well, again, I just followed through an article, this time, none other than Scott Hanselman. I have nothing more to say but to follow this article to the line, I can vouch that it works. 😎
Article: Securing an Azure App Service Website under SSL in minutes with Let's Encrypt
When I said to follow it to the line, please do so, I was kind of dumb enough to do some silly mistakes like below,
- Getting subscription id wrong 🤦♂️
- Setting up permissions to the app incorrectly - make sure you add
Contribute
permissions to the resource group. You may get different errors from the LetsEncrypt configuration page if you haven't done that, which makes you fix non-existing problems.
After overcoming my own mistakes, my site was setup with LetsEncrypt SSL.
Azure Application Insights for Gatsby web site
Well, this is where I had real trouble with, I have went through the rabbit hole of polyfills to Azure Application Insights documentations and JavaScript repository to god-knows-where. To be honest, I'm actually not sure this is really an issue with my setup or something wrong I have done. I will update here if I discover the true reason for it.
First of all, good reads to start,
Implementing Monitoring in React Using AppInsights by Aaron Powell
Gatsby with Azure AppInsights by Daniel Oliver
Having read above-mentioned articles, I wanted to add a few more features as well. I wanted my GitHub Actions to pick Azure Application Insights key from its secrets, which made me read about few other functionalities in Gatsby.
My approach here was to use environment variables in Gatsby. You can read about them here. My idea was to use .env
locally and then pass AAI key as a parameter to build function.
So, I've added .env
to the application folder (which is ignored and not added the repo), updated it with below content,
GATSBY_APPLICATION_INSIGHTS_KEY={Azure-Application-Insights-Key}
Now I have to update my gatsby-config.js
to accept values from .env
file, added below section to the top to make it happen,
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
})
Now the interesting part, where I'm going to use the AAI key, for this, I simply copied the code mentioned in Aaron Powell's article, and added small change to it, my AppInsights.js
looked like this,
// AppInsights.js
import { ApplicationInsights } from '@microsoft/applicationinsights-web'
import { ReactPlugin, withAITracking } from '@microsoft/applicationinsights-react-js'
import { globalHistory } from "@reach/router"
const reactPlugin = new ReactPlugin();
const ai = new ApplicationInsights({
config: {
instrumentationKey: process.env.GATSBY_APPLICATION_INSIGHTS_KEY,
extensions: [reactPlugin],
extensionConfig: {
[reactPlugin.identifier]: { history: globalHistory }
}
}
})
if (process.env.GATSBY_APPLICATION_INSIGHTS_KEY) {
ai.loadAppInsights();
} else {
console.log('Application insights key not available.')
}
export default (Component) => withAITracking(reactPlugin, Component)
export const appInsights = ai.appInsights
The change I added was between lines 17-21, where it invokes ai.loadAppInsights()
only when process.env.GATSBY_APPLICATION_INSIGHTS_KEY
is present. After completing this, I had to change my GitHub actions to include AAI key, which I have done as below, Line 32
name: bitsnorbytes.com build and deploy to Azure Web App
on:
[push]
env:
AZURE_WEBAPP_NAME: bitsnorbytes
AZURE_WEBAPP_PACKAGE_PATH: './public'
NODE_VERSION: '10.x'
jobs:
build-and-deploy:
name: Build and Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2.3.1
with:
persist-credentials: false
- name: Install npm ${{ env.NODE_VERSION }} 🔧
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install packages 🔧
run: |
npm install
- name: Build project 🔧
run: |
GATSBY_APPLICATION_INSIGHTS_KEY=${{ secrets.APPLICATION_INSIGHTS_KEY }} npm run-script build
- name: Deploy to Azure WebApp 🚀
uses: azure/webapps-deploy@v1
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
@microsoft/applicationinsights-web@2.5.6
error
This section is dedicated to few errors I got when I was using @microsoft/applicationinsights-web@2.5.6
, this error has been fixed in @microsoft/applicationinsights-web@2.5.7
. Please ignore this section if are already using @microsoft/applicationinsights-web@2.5.7
or later, but if you are seeing this error on a different context, reading through this may help you to find an answer.
At the time I writing this article, I was using @microsoft/applicationinsights-web@2.5.6
for analytics and when I used AppInsights.js
mentioned above, I got an error like this.
WebpackError: ReferenceError: XMLHttpRequest is not defined
The full error looked like this,
Building static HTML failed
See our docs page for more info on this error: https://gatsby.dev/debug-html
143 | }
144 | else {
> 145 | if (!CoreUtils.isUndefined(XMLHttpRequest)) {
| ^
146 | var testXhr = new XMLHttpRequest();
147 | if ("withCredentials" in testXhr) {
148 | this._sender = this._xhrSender;
WebpackError: ReferenceError: XMLHttpRequest is not defined
- Sender.js:145
node_modules/@microsoft/applicationinsights-channel-js/dist-esm/Sender.js:145:27
- TelemetryHelpers.js:40
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/TelemetryHelpers.js:40:1
- CoreUtils.js:183
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/CoreUtils.js:183:1
- TelemetryHelpers.js:39
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/TelemetryHelpers.js:39:14
- ChannelController.js:85
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/ChannelController.js:85:78
- CoreUtils.js:183
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/CoreUtils.js:183:1
- ChannelController.js:85
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/ChannelController.js:85:1
- TelemetryHelpers.js:40
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/TelemetryHelpers.js:40:1
- CoreUtils.js:183
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/CoreUtils.js:183:1
- TelemetryHelpers.js:39
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/TelemetryHelpers.js:39:14
- BaseCore.js:100
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/BaseCore.js:100:26
- AppInsightsCore.js:18
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/AppInsightsCore.js:18:1
- Initialization.js:233
node_modules/@microsoft/applicationinsights-web/dist-esm/Initialization.js:233:1
- AppInsights.js:20
src/services/AppInsights.js:20:8
This was strange for me because of two reasons,
- Why build tries to invoke
ai.loadAppInsights()
on build time, shouldn't it be called only at run time? - How it can be that
XMLHttpRequest
is not available for build anyways, isn't it something which should be there in all environments?
Answer for the first question is still at large, I think it's something related to how Gatsby
build applications. I am still to learn about it.
For the second question though, it turned out that XMLHttpRequest
is not available for the Node.js and should be added as a package. The reason I said 'a' package earlier because, there are two packages available for us to use.
xmlhttprequest
: https://www.npmjs.com/package/xmlhttprequestxhr2
: https://www.npmjs.com/package/xhr2
For my case though, the first package did not work as expected, I have came across this article: http://zuga.net/articles/node-errors-referenceerror-xmlhttprequest-is-not-defined/, which talks about how to use xmlhttprequest
to fix this error. But setting it as global variable didn't work.
Then I found this answer by Matt Kane. Seeing that, I updated my AppInsights.js
like this,
// AppInsights.js
import { ApplicationInsights } from '@microsoft/applicationinsights-web'
import { ReactPlugin, withAITracking } from '@microsoft/applicationinsights-react-js'
import { globalHistory } from "@reach/router"
if (!("XMLHttpRequest" in global)) {
global.XMLHttpRequest = require('xhr2');
}
....
Check lines 6-9, it checks if XMLHttpRequest
is available on the global
object, if it's not there, it creates XMLHttpRequest
from xhr2
.
Now you may think that this solved my problems, I wished at the time the same too, but my luck was not there for it. Now I was smashed with new error,
WebpackError: ReferenceError: XDomainRequest is not defined
Full error as below,
Building static HTML failed
See our docs page for more info on this error: https://gatsby.dev/debug-html
149 | this._XMLHttpRequestSupported = true;
150 | }
> 151 | else if (!CoreUtils.isUndefined(XDomainRequest)) {
| ^
152 | this._sender = this._xdrSender; // IE 8 and 9
153 | }
154 | }
WebpackError: ReferenceError: XDomainRequest is not defined
- Sender.js:151
node_modules/@microsoft/applicationinsights-channel-js/dist-esm/Sender.js:151:36
- TelemetryHelpers.js:40
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/TelemetryHelpers.js:40:1
- CoreUtils.js:183
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/CoreUtils.js:183:1
- TelemetryHelpers.js:39
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/TelemetryHelpers.js:39:14
- ChannelController.js:85
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/ChannelController.js:85:78
- CoreUtils.js:183
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/CoreUtils.js:183:1
- ChannelController.js:85
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/ChannelController.js:85:1
- TelemetryHelpers.js:40
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/TelemetryHelpers.js:40:1
- CoreUtils.js:183
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/CoreUtils.js:183:1
- TelemetryHelpers.js:39
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/TelemetryHelpers.js:39:14
- BaseCore.js:100
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/BaseCore.js:100:26
- AppInsightsCore.js:18
[applicationinsights-web]/[@microsoft]/applicationinsights-core-js/dist-esm/JavaScriptSDK/AppInsightsCore.js:18:1
- Initialization.js:233
node_modules/@microsoft/applicationinsights-web/dist-esm/Initialization.js:233:1
- AppInsights.js:20
src/services/AppInsights.js:20:8
Now, I thought as for XMLHttpRequest
, there would be a package to import for XDomainRequest
as well. It turned out that there is no package for XDomainRequest
. That left me no option but to check out the code within applicationinsights-web
for a possible solution. That's when I actually noticed the code which it threw the error in the first place,
ApplicationInsights-JS/channels/applicationinsights-channel-js/src/Sender.ts
if (!CoreUtils.isUndefined(XMLHttpRequest)) {
const testXhr = new XMLHttpRequest();
if ("withCredentials" in testXhr) {
_self._sender = _xhrSender;
_self._XMLHttpRequestSupported = true;
} else if (!CoreUtils.isUndefined(XDomainRequest)) {
_self._sender = _xdrSender; // IE 8 and 9
}
}
What this actually means is that, if XMLHttpRequest
or XDomainRequest
is undefined
, AAI should work without any issue. So I actually do not need xhr2
in the first place. With that in mind, I've changed my AppInsights.js
as below,
// AppInsights.js
import { ApplicationInsights } from '@microsoft/applicationinsights-web'
import { ReactPlugin, withAITracking } from '@microsoft/applicationinsights-react-js'
import { globalHistory } from "@reach/router"
if (!("XMLHttpRequest" in global)) {
global.XMLHttpRequest = undefined;
global.XDomainRequest = undefined;
}
Now when I ran gatsby build
, it worked without any issue was what I wanted to say, but I didn't, now I was hammered with new error,
Building static HTML failed
See our docs page for more info on this error: https://gatsby.dev/debug-html
234 | snippetVer += ".lg";
235 | }
> 236 | _this.properties.context.internal.snippetVer = snippetVer || "-";
| ^
237 | // apply updated properties to the global instance (snippet)
238 | for (var field in _this) {
239 | if (CoreUtils.isString(field) &&
WebpackError: TypeError: Cannot set property 'snippetVer' of undefined
- Initialization.js:236
node_modules/@microsoft/applicationinsights-web/dist-esm/Initialization.js:236:1
- Initialization.js:263
node_modules/@microsoft/applicationinsights-web/dist-esm/Initialization.js:263:1
- AppInsights.js:23
src/services/AppInsights.js:23:8
- 404.js:1
src/pages/404.js:1:1
This error was easy to fix though, when I googled for this error, I came across this GitHub issue.
https://github.com/microsoft/ApplicationInsights-JS/issues/1321
There I took the hint that, this specific issue may be caused by @microsoft/applicationinsights-web@2.5.6
, so I installed @microsoft/applicationinsights-web@2.5.3
which mentioned in that ticket, and VOILA!!!, it stopped me giving this error.
This issue has been fixed with @microsoft/applicationinsights-web@2.5.7
, you can find more details about it in below GitHub issue. Thank you kryalama for the fix! At the moment, @MSNev opened the discussion of this error in below issue, in the future, if someone needs more details about this, you may check out this issue for more details.
https://github.com/microsoft/ApplicationInsights-JS/issues/1334
At the time For now though, my final AppInsights.js
looks like this and it works without any issue for the build.
Azure Application Insights version: @microsoft/applicationinsights-web@2.5.3
// AppInsights.js
import { ApplicationInsights } from '@microsoft/applicationinsights-web'
import { ReactPlugin, withAITracking } from '@microsoft/applicationinsights-react-js'
import { globalHistory } from "@reach/router"
if (!("XMLHttpRequest" in global)) {
global.XMLHttpRequest = undefined;
global.XDomainRequest = undefined;
}
const reactPlugin = new ReactPlugin();
const ai = new ApplicationInsights({
config: {
instrumentationKey: process.env.GATSBY_APPLICATION_INSIGHTS_KEY,
extensions: [reactPlugin],
extensionConfig: {
[reactPlugin.identifier]: { history: globalHistory }
}
}
})
if (process.env.GATSBY_APPLICATION_INSIGHTS_KEY) {
ai.loadAppInsights();
} else {
console.log('Application insights key not available.')
}
export default (Component) => withAITracking(reactPlugin, Component)
export const appInsights = ai.appInsights
Conclusion
Throughout the process of converting my site to Gatsby
was a big learning experience for me. I learned a lot about Gatsby
, about GitHub Actions, Azure WebSites with LetsEncrypt encryption, and finally, integration to Azure Application Insights to static web sites. Though I had few troubles with Azure Application Insights initially, the workaround I mentioned here keep my site up and running at the time, then quick solution from kryalama allowed me to use the tooling as it meant to be. For the last one though, I'm still not sure if this is an issue with how I setup the site or an issue with how With that, my adventure with setting up the Gatsby site ended with remarkable results. 😁😁Gatsby
build applications. Either way, I have a working fix which allows me to continue using Application Insights in my site. I call it a win. 😊
Hope you learned something through this experience of mine. Cheers!