Hosting this blog on Azure and switching to a static site setup

My experiences from hosting an Optimizely CMS site on Azure and moving to a generated static site on Netlify.

One benefit of being an Optimizely MVP is to get a free Content Cloud license. I used such a license for the previous version of this blog and for many years going from CMS 6 to 11.

When Azure compatibility came around I moved it my own Azure subscription and tried to get the consumption down as low as possible.

Key actions to reduce Azure cost

  • No SSL setup on custom domain in Azure App Service.
  • Proxy requests through Cloudflare Free to get HTTPS.
  • Run Scheduled Jobs with low frequency.
  • Remove any Pingdom-like monitoring.

The typical Azure-month

With added sales tax I was normally invoiced below 15 EUR (160 SEK) per month.

I was on the Shared App Service and Single Basic SQL Database plans.

Azure cost analysis for June 2022 around 9 EUR App Service and 5 EUR SQL Database

Upgrading to CMS 12 and switching to Linux App Service or one of the other Docker hosting options on Azure would probably reduce cost further but I chose to take a different route.

Migrating to Record Collector and Netlify

If you are unfamiliar with static site generators they typically work with content files that has the content and a file header with YAML or JSON meta data often referred to as front matter.

As an example this post's file looks like this.

{
    "title": "Hosting this blog on Azure and switching to a static site setup",
    "date": "2022-08-15 16:00",
    "description": "Experiences from hosting an Optimizely CMS site on Azure and moving to a generated static site on Netlify.",
    "categories": ["Episerver", "ASP.NET", "Netlify"]
}
<p>My experiences from hosting an Optimizely CMS site on Azure and moving to a generated static site on Netlify.</p>
<!--more-->
<p>One benefit of being an Optimizely MVP is to get a free Content Cloud license. I used such a license for the previous version of this blog and for many years going from CMS 6 to 11.</p>

<p>When Azure compatibility came around I moved it my own Azure subscription and tried to get the cost down as low as possible.</p>

<h2>My Key actions to reduce Azure consumption</h2>

<ul class="prose-list">
    <li>No SSL setup on custom domain in Azure App Service.</li>
    <li>Proxy requests through Cloudflare Free to get HTTPS.</li>
    <li>Run Scheduled Jobs with low frequency.</li>
    <li>Remove any Pingdom-like monitoring.</li>
</ul>

<h2>The typical Azure-month</h2>
..

If you work with Markdown it's very similar but the file is named .md and has Markdown syntax as content.

If the post has image files I like to put them directly in the month folder.

Some favor having each post in its own folder but then you get the problem that all posts files are called index.md or index.html which I don't like.

PNG files in the same month folder as post file

There are some CMS options that work with static site generators such as Forestry but I prefer to work in VS Code and just add a Markdown preview and spell checker extension.

Usually I verify the site locally in Record Collector's ASP.NET MVC mode to get instant preview and then do a push of main to GitHub which triggers a Netlify build.

In the Netlify deploy I have turned on image files optimization and some plugins.

Installed Netlify plugins: A11y, Checklinks and HTML validate

The plugins will check all generated HTML files and block from publishing if any errors are found.

Any errors from for example HTML validate are in the build output.

artifacts/static-site/2013/01/avoid-uppercase-letters-in-your-episerver-page-url/index.html
    49:1   error  Trailing whitespace                                           no-trailing-whitespace
    75:61  error  Mismatched close-tag, expected '</p>' but found '</a>'        close-order
    75:66  error  Mismatched close-tag, expected '</div>' but found '</p>'      close-order
    76:2   error  Mismatched close-tag, expected '</main>' but found '</div>'   close-order
    87:4   error  Mismatched close-tag, expected '</div>' but found '</main>'   close-order
    88:3   error  Mismatched close-tag, expected '</body>' but found '</div>'   close-order
    89:3   error  Element <footer> is not permitted as content in <html>        element-permitted-content
   143:2   error  Mismatched close-tag, expected '</html>' but found '</body>'  close-order
   144:2   error  Unexpected close-tag, expected opening tag                    close-order

Checks like these are invaluable for finding mistakes.

My plan going forward is to, instead of just writing posts, try to add more creative HTML parts to the content using Tailwind CSS classes. To enable this I'm not using the Typography plugin for Tailwind which I think will interfere too much with having more complex markup inside the content.

For now I have set up this lighter version that also uses .prose as class name instead.

@layer components {
    .outline-primary {
        @apply focus:outline-pink-600 focus:outline-dotted focus:outline-offset-1 focus:outline-[3px] hover:outline-pink-600 hover:outline-dotted hover:outline-offset-1 hover:outline-[3px] active:outline-green-600 active:outline-dotted active:outline-offset-1 active:outline-[3px];
    }

    .selected-primary {
        @apply outline-white outline-dotted outline-offset-1 outline-[3px];
    }

    .link-primary {
        @apply font-medium underline text-blue-logo;
    }

    .prose > * + *,
    .prose li + li,
    .prose li > p + p {
        @apply mt-6;
    }
    .prose em {
        @apply font-bold not-italic;
    }
    .prose strong {
        @apply font-bold;
    }
    .prose a {
        @apply outline-primary link-primary;
    }
    .prose h2 {
        @apply mt-16 -mb- text-lg tracking-tight font-bold md:text-xl;
    }
    .prose h3 {
        @apply mt-12 -mb-4 text-base tracking-tight font-bold md:text-lg;
    }
    .prose code {
        @apply font-mono inline font-semibold;
    }
    .prose code::before {
        content: "`";
    }
    .prose code::after {
        content: "`";
    }
    .prose pre code::before {
        content: "";
    }
    .prose pre code::after {
        content: "";
    }
    .prose pre {
        @apply block bg-gray-100 pl-3 pb-3 pt-3 rounded-lg text-sm overflow-x-auto focus-visible:outline-pink-600 focus-visible:outline-dotted focus-visible:outline-offset-1 focus-visible:outline-[3px];
    }
    .prose pre code {
        @apply inline-table bg-transparent pr-3 font-medium;
    }
    .prose blockquote {
        @apply border-l-4 pl-3 text-base md:text-lg py-1 text-gray-600;
    }
    .prose blockquote cite {
        @apply text-sm md:text-base text-gray-900 not-italic;
    }
    .prose ul,
    .prose ol {
        @apply space-y-2;
    }
    .prose ul.prose-list {
        @apply list-disc list-outside ml-6 marker:text-pink-600;
    }
    .prose ol.prose-list {
        @apply list-decimal list-outside ml-6 marker:font-semibold;
    }
}

A benefit with this is that it doesn't suffer from the collapsed right padding problem that Firefox has when having horizontal overflow and scrolling like what most code samples have.

Check out the migrated content and stay tuned for more updates.

Reach out if you're interested in the setup or need guidance with Record Collector.

Published and tagged with these categories: Optimizely, CMS, Azure, ASP.NET, Netlify