<!DOCTYPE html>
<html lang="en" id="home">

<meta http-equiv="content-type" content="text/html;charset=utf-8" />

<head>
    <title>
        <%= blogPost.title %>
    </title>
    <meta name="description" content="<%= blogPost.description %>">
    <%- include('../head-basic') -%>
        <link rel="canonical" href="https://oneuptime.com/blog/post/<%= blogPost.fileName %>/view" />
        <meta property="og:site_name" content="OneUptime | One Complete Observability platform.">
        <meta property="og:type" content="article">
        <meta property="og:title" content="<%= blogPost.title %>">
        <meta property="og:description" content="<%= blogPost.description %>">
        <meta property="og:url" content="<%= blogPost.blogUrl %>">
        <meta property="og:image" content="<%= blogPost.socialMediaImageUrl %>">
        <meta property="article:published_time" content="<%- blogPost.postDate -%>T00:00:00.000Z">
        <meta property="article:modified_time" content="<%- blogPost.postDate -%>T00:00:00.000Z">
        <meta property="article:publisher" content="https://www.facebook.com/OneUptime">
        <meta name="twitter:card" content="summary_large_image">
        <meta name="twitter:title" content="<%= blogPost.title %>">
        <meta name="twitter:description" content="<%= blogPost.description %>">
        <meta name="twitter:url" content="<%= blogPost.blogUrl %>">
        <meta name="twitter:image" content="<%= blogPost.socialMediaImageUrl %>">
        <meta name="twitter:label1" content="Written by">
        <meta name="twitter:data1" content="<%= blogPost.author.name %>">
        <meta name="twitter:site" content="@OneUptimeHQ">
        <meta property="og:image:width" content="1280">
        <meta property="og:image:height" content="720">
        <script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "Article",
    "publisher": {
        "@type": "Organization",
        "name": "OneUptime",
        "url": "https://oneuptime.com/",
        "logo": {
            "@type": "ImageObject",
            "url": "https://oneuptime.com/img/OneUptimePNG/1.png",
            "width": 60,
            "height": 60
        }
    },
    "author": {
        "@type": "Person",
        "name": "<%= blogPost.author.name %>",
        "url": "<%= blogPost.author.githubUrl %>",
        "sameAs": []
    },
    "headline": "<%= blogPost.title %>",
    "url": "<%= blogPost.blogUrl %>",
    "datePublished": "<%- blogPost.postDate -%>T00:00:00.000Z",
    "dateModified": "<%- blogPost.postDate -%>T00:00:00.000Z",
    "description": "<%- blogPost.description -%>",
    "mainEntityOfPage": "<%= blogPost.blogUrl %>"
}
    </script>

        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/a11y-dark.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/dockerfile.min.js"></script>
        <script>hljs.highlightAll();</script>

        <style>
            body {
                background: radial-gradient(circle at top, #eef2ff, #f8fafc 45%, #ffffff 70%);
                color: #111827;
            }

            .reading-progress {
                position: fixed;
                top: 0;
                left: 0;
                width: 0;
                height: 4px;
                z-index: 60;
                background: linear-gradient(90deg, #4f46e5, #7c3aed);
                transition: width 120ms ease-out;
            }

            .floating-share {
                position: absolute;
                left: -5rem;
                top: 0;
                display: flex;
                flex-direction: column;
                gap: 0.75rem;
            }

            .floating-share a {
                width: 2.75rem;
                height: 2.75rem;
                border-radius: 9999px;
                border: 1px solid rgba(148, 163, 184, 0.5);
                display: inline-flex;
                align-items: center;
                justify-content: center;
                background: rgba(255, 255, 255, 0.9);
                box-shadow: 0 10px 30px rgba(79, 70, 229, 0.08);
                transition: all 0.2s ease;
            }

            .floating-share a:hover {
                border-color: #4f46e5;
                color: #4f46e5;
                transform: translateY(-2px);
            }

            /* Custom styles for code blocks in blog posts */
            .blog-body pre {
                font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Consolas', 'Courier New', monospace !important;
                font-size: 14px !important;
                line-height: 1.5 !important;
                font-weight: 400 !important;
                margin-top: 1.75rem !important;
                margin-bottom: 1.75rem !important;
                border: 1px solid rgba(15, 23, 42, 0.08) !important;
                box-shadow: 0 20px 45px rgba(15, 23, 42, 0.08) !important;
            }
            
            .blog-body pre code {
                font-family: inherit !important;
                font-size: inherit !important;
                line-height: inherit !important;
                font-weight: inherit !important;
            }
            
            /* Ensure highlight.js doesn't override our font settings */
            .blog-body .hljs {
                font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Consolas', 'Courier New', monospace !important;
                font-size: 14px !important;
                line-height: 1.5 !important;
                font-weight: 400 !important;
            }

            /* Inline code chips */
            .blog-body :not(pre) > code {
                font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Consolas', 'Courier New', monospace;
                font-size: 0.80em;
                padding: 0.15rem 0.35rem;
                border-radius: 0.45rem;
                background: rgba(148, 163, 184, 0.15);
                border: 1px solid rgba(71, 85, 105, 0.35);
                color: #0f172a;
                box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45);
                white-space: nowrap;
            }

            .blog-body :not(pre) > code strong {
                font-weight: 600;
            }

            .blog-body blockquote {
                border-left: 4px solid #a5b4fc;
                background: rgba(224, 231, 255, 0.45);
                padding: 1.25rem 1.5rem;
                border-radius: 1rem;
                font-style: normal;
                color: #1f2937;
                box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
            }

            .blog-body img {
                border-radius: 1.25rem;
                box-shadow: 0 25px 60px rgba(15, 23, 42, 0.08);
            }

            .blog-body h2,
            .blog-body h3,
            .blog-body h4 {
                scroll-margin-top: 7rem;
            }

            @media (max-width: 1279px) {
                .floating-share {
                    display: none !important;
                }
            }
        </style>

</head>


<body>
    <div class="reading-progress" id="reading-progress"></div>
    <%- include('../nav') -%>

        <div class="relative isolate overflow-hidden bg-gradient-to-b from-white via-white to-slate-50/40">
            <!-- Hero / Title Section -->
            <div class="pt-20 pb-12 sm:pt-28 sm:pb-20">
                <div class="mx-auto max-w-4xl px-6 lg:px-8 text-center relative">
                    <h1 class="text-4xl sm:text-5xl font-extrabold tracking-tight text-gray-900 leading-tight">
                        <%= blogPost.title %>
                    </h1>
                    <p class="mt-6 text-xl text-gray-600 leading-relaxed max-w-3xl mx-auto">
                        <%= blogPost.description %>
                    </p>
                    <div class="mt-8 flex flex-wrap items-center justify-center gap-4 text-sm text-gray-500">
                        <% if(blogPost.author){ %>
                        <a href="<%- blogPost.author.githubUrl -%>" target="_blank" class="flex items-center space-x-2 group">
                            <!-- Added explicit width & height to stabilize layout (CLS) -->
                            <img class="h-8 w-8 rounded-full ring-2 ring-indigo-200 group-hover:ring-indigo-400 transition" src="<%- blogPost.author.profileImageUrl -%>" alt="<%- blogPost.author.name -%>" width="32" height="32" decoding="async">
                            <span class="font-medium text-gray-700 group-hover:text-gray-900 transition">@<%- blogPost.author.username -%></span>
                        </a>
                        <span class="hidden sm:inline select-none">•</span>
                        <span><%- blogPost.formattedPostDate -%></span>
                        <span class="hidden sm:inline select-none">•</span>
                        <% } %>
                        <span id="reading-time" class="inline-flex items-center gap-1">
                            <svg class="w-4 h-4 text-indigo-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
                            <span class="sr-only">Reading time</span>
                        </span>
                    </div>
                    <div class="mt-8">
                        <%- include('./Partials/Tags', { blogPost: blogPost }) -%>
                    </div>
                </div>
            </div>

            <!-- Content + Sidebar -->
            <div class="pb-24">
                <div class="mx-auto max-w-7xl px-6 lg:px-8">
                    <div class="grid grid-cols-1 lg:grid-cols-12 gap-12">
                        <!-- Main content -->
                        <article class="lg:col-span-8 xl:col-span-9">
                            <div class="relative">
                                <div class="floating-share hidden xl:flex" aria-label="Share this article">
                                    <a title="Share on X" target="_blank" rel="noopener" href="https://twitter.com/intent/tweet?text=<%- encodeURIComponent(blogPost.title) -%>&url=<%- encodeURIComponent(blogPost.blogUrl) -%>">
                                        <svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M17.53 3h3.77l-8.26 9.45L23 21h-6.17l-4.8-6.01L6.4 21H2.62l8.63-9.87L1 3h6.32l4.33 5.41L17.53 3Zm-1.33 15.62h2.09L7.94 4.29H5.71l10.49 14.33Z"/></svg>
                                    </a>
                                    <a title="Share on LinkedIn" target="_blank" rel="noopener" href="https://www.linkedin.com/sharing/share-offsite/?url=<%- encodeURIComponent(blogPost.blogUrl) -%>">
                                        <svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.049c.476-.9 1.637-1.85 3.37-1.85 3.601 0 4.266 2.37 4.266 5.455v6.286ZM5.337 7.433a2.062 2.062 0 1 1 0-4.124 2.062 2.062 0 0 1 0 4.124ZM7.119 20.452H3.553V9h3.566v11.452ZM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003Z"/></svg>
                                    </a>
                                    <a title="Discuss on Hacker News" target="_blank" rel="noopener" href="https://news.ycombinator.com/submitlink?u=<%- encodeURIComponent(blogPost.blogUrl) -%>&t=<%- encodeURIComponent(blogPost.title) -%>">
                                        <svg class="w-4 h-4" viewBox="0 0 24 24" aria-hidden="true">
                                            <circle cx="12" cy="12" r="10" fill="currentColor" opacity="0.92"></circle>
                                            <path d="M7.2 6.2L12 12.8l4.8-6.6M12 13.4V17.8" fill="none" stroke="#ffffff" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"></path>
                                        </svg>
                                    </a>
                                </div>
                                <div class="blog-body prose prose-slate max-w-none prose-headings:scroll-mt-28 prose-headings:font-display prose-headings:font-semibold lg:prose-headings:scroll-mt-[8.5rem] prose-a:font-semibold prose-a:text-indigo-600 hover:prose-a:text-indigo-500 prose-img:rounded-xl prose-pre:rounded-xl prose-code:text-indigo-600">
                                    <%- blogPost.htmlBody -%>
                                </div>
                            </div>

                            <!-- Share -->
                            <div class="mt-14 border-t border-gray-200/70 pt-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-6">
                                <div class="text-sm font-medium text-gray-600">Share this article</div>
                                <div class="flex gap-3">
                                    <a title="Share on X" target="_blank" rel="noopener" class="group inline-flex items-center justify-center h-9 w-9 rounded-lg border border-gray-200 hover:border-indigo-400 hover:bg-indigo-50 transition" href="https://twitter.com/intent/tweet?text=<%- encodeURIComponent(blogPost.title) -%>&url=<%- encodeURIComponent(blogPost.blogUrl) -%>">
                                        <svg class="w-4 h-4 text-gray-500 group-hover:text-indigo-600" viewBox="0 0 24 24" fill="currentColor"><path d="M17.53 3h3.77l-8.26 9.45L23 21h-6.17l-4.8-6.01L6.4 21H2.62l8.63-9.87L1 3h6.32l4.33 5.41L17.53 3Zm-1.33 15.62h2.09L7.94 4.29H5.71l10.49 14.33Z"/></svg>
                                    </a>
                                    <a title="Share on LinkedIn" target="_blank" rel="noopener" class="group inline-flex items-center justify-center h-9 w-9 rounded-lg border border-gray-200 hover:border-indigo-400 hover:bg-indigo-50 transition" href="https://www.linkedin.com/sharing/share-offsite/?url=<%- encodeURIComponent(blogPost.blogUrl) -%>">
                                        <svg class="w-4 h-4 text-gray-500 group-hover:text-indigo-600" viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.049c.476-.9 1.637-1.85 3.37-1.85 3.601 0 4.266 2.37 4.266 5.455v6.286ZM5.337 7.433a2.062 2.062 0 1 1 0-4.124 2.062 2.062 0 0 1 0 4.124ZM7.119 20.452H3.553V9h3.566v11.452ZM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003Z"/></svg>
                                    </a>
                                    <a title="Discuss on Hacker News" target="_blank" rel="noopener" class="group inline-flex items-center justify-center h-9 w-9 rounded-lg border border-gray-200 hover:border-indigo-400 hover:bg-indigo-50 transition" href="https://news.ycombinator.com/submitlink?u=<%- encodeURIComponent(blogPost.blogUrl) -%>&t=<%- encodeURIComponent(blogPost.title) -%>">
                                        <svg class="w-4 h-4 text-gray-500 group-hover:text-indigo-600" viewBox="0 0 24 24" aria-hidden="true">
                                            <circle cx="12" cy="12" r="10" fill="currentColor" opacity="0.92"></circle>
                                            <path d="M7.2 6.2L12 12.8l4.8-6.6M12 13.4V17.8" fill="none" stroke="#ffffff" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"></path>
                                        </svg>
                                    </a>
                                </div>
                            </div>

                            <!-- Author -->
                            <% if(blogPost.author){ %>
                            <div class="mt-16 p-6 rounded-2xl bg-white/60 backdrop-blur border border-gray-200 shadow-sm flex gap-6 items-start">
                                <!-- Added explicit width/height + lazy loading for below-the-fold author bio image -->
                                <img class="h-16 w-16 rounded-full ring-2 ring-indigo-200" src="<%- blogPost.author.profileImageUrl -%>" alt="<%- blogPost.author.name -%>" width="64" height="64" loading="lazy" decoding="async">
                                <div>
                                    <h3 class="text-lg font-semibold text-gray-900"><%- blogPost.author.name -%></h3>
                                    <p class="text-sm text-gray-600 mb-2">@<%- blogPost.author.username -%> • <%- blogPost.formattedPostDate -%> • <span id="reading-time-inline"></span></p>
                                    <div class="text-sm text-gray-700"><%- blogPost.author.bio || 'Building reliable software at OneUptime. Follow along for more on observability & reliability.' -%></div>
                                    <div class="mt-3 flex gap-3">
                                        <a href="<%- blogPost.author.githubUrl -%>" target="_blank" class="text-xs font-medium text-indigo-600 hover:text-indigo-500 inline-flex items-center gap-1">View GitHub
                                            <svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13 5h6m0 0v6m0-6L10 14"/></svg>
                                        </a>
                                    </div>
                                </div>
                            </div>
                            <% } %>

                            <div class="mt-20">
                                <%- include('./Partials/OpenSourceCommitment', { blogPost: blogPost }) -%>
                            </div>
                        </article>

                        <!-- Sidebar -->
                        <aside class="hidden lg:block lg:col-span-4 xl:col-span-3">
                            <div class="sticky top-28 space-y-8">
                                <div class="rounded-2xl border border-gray-200 bg-white/70 backdrop-blur p-6 shadow-sm">
                                    <h2 class="text-sm font-semibold tracking-wide text-gray-900 uppercase mb-4">On this page</h2>
                                    <!-- Reserve initial space for TOC to reduce layout shift once populated -->
                                    <nav id="toc" aria-label="Table of contents" class="text-sm leading-6 space-y-2 text-gray-700 min-h-[3rem]"></nav>
                                </div>
                            </div>
                        </aside>
                    </div>
                </div>
            </div>
        </div>




        <%- include('./Partials/BlogCta') -%>
            <%- include('../footer') -%>
            <script>
                (function(){
                    // Reading time
                    try {
                        const container = document.querySelector('.blog-body');
                        if(container){
                            const text = container.textContent || '';
                            const words = text.trim().split(/\s+/).filter(Boolean).length;
                            const minutes = Math.max(1, Math.round(words / 200));
                            const rt = minutes + ' min read';
                            const el = document.getElementById('reading-time');
                            if(el){ el.insertAdjacentText('beforeend', rt); }
                            const el2 = document.getElementById('reading-time-inline');
                            if(el2){ el2.textContent = rt; }
                        }
                    } catch (e) {}

                    // Reading progress indicator
                    try {
                        const progressBar = document.getElementById('reading-progress');
                        const target = document.querySelector('.blog-body');
                        if(progressBar && target){
                            let start = 0;
                            let end = 0;
                            const recalc = () => {
                                const rect = target.getBoundingClientRect();
                                const scrollTop = window.scrollY || window.pageYOffset;
                                start = scrollTop + rect.top - 120;
                                end = start + target.offsetHeight;
                            };
                            const update = () => {
                                if(!end){ recalc(); }
                                const scrollPos = window.scrollY || window.pageYOffset;
                                const distance = end - start;
                                if(distance <= 0){ return; }
                                const progress = Math.min(1, Math.max(0, (scrollPos - start) / distance));
                                progressBar.style.width = (progress * 100).toFixed(2) + '%';
                            };
                            recalc();
                            window.addEventListener('scroll', update, { passive: true });
                            window.addEventListener('resize', () => {
                                end = 0;
                                recalc();
                                update();
                            });
                            update();
                        }
                    } catch (e) {}

                    // TOC
                    try {
                        const container = document.querySelector('.blog-body');
                        if(!container){ return; }
                        const headings = container.querySelectorAll('h2, h3');
                        if(!headings.length){ return; }
                        const toc = document.getElementById('toc');
                        if(!toc){ return; }
                        headings.forEach(h => {
                            if(!h.id){
                                h.id = h.textContent.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'');
                            }
                            const a = document.createElement('a');
                            a.href = '#' + h.id;
                            a.textContent = h.textContent;
                            a.className = 'block hover:text-indigo-600 transition ' + (h.tagName === 'H3' ? 'pl-4 text-gray-500' : 'font-medium');
                            toc.appendChild(a);
                        });
                    } catch (e) {}

                    // Stabilize images inside blog body (add width/height if missing & lazy-load non-hero images)
                    try {
                        const imgs = document.querySelectorAll('.blog-body img');
                        imgs.forEach((img, index) => {
                            // If width/height already specified, skip dimension inference
                            if(!img.hasAttribute('width') || !img.hasAttribute('height')){
                                if(img.naturalWidth && img.naturalHeight){
                                    img.setAttribute('width', img.naturalWidth);
                                    img.setAttribute('height', img.naturalHeight);
                                } else {
                                    // If not yet loaded, attach a one-time listener
                                    img.addEventListener('load', function handler(){
                                        if(!img.hasAttribute('width') && img.naturalWidth){ img.setAttribute('width', img.naturalWidth); }
                                        if(!img.hasAttribute('height') && img.naturalHeight){ img.setAttribute('height', img.naturalHeight); }
                                        img.removeEventListener('load', handler);
                                    });
                                }
                            }
                            // Lazy-load images after the first one (often top/hero) for better LCP while reducing CLS
                            if(index > 0 && !img.hasAttribute('loading')){
                                img.setAttribute('loading', 'lazy');
                            }
                            if(!img.hasAttribute('decoding')){
                                img.setAttribute('decoding', 'async');
                            }
                            // Ensure display block for centered images without causing late shifts
                            if(!img.className.includes('inline') && !img.className.includes('block')){
                                img.classList.add('block');
                            }
                        });
                    } catch(e) {}

                    // Mermaid diagrams
                    try {
                        const mermaidCodeBlocks = document.querySelectorAll('.blog-body pre code.language-mermaid, .blog-body pre code.mermaid, .blog-body pre code[class*="language-mermaid"], .blog-body pre code[class*="mermaid"]');
                        if(mermaidCodeBlocks.length){
                            // Dynamically load Mermaid only if needed to avoid extra payload on pages without diagrams
                            const script = document.createElement('script');
                            script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js';
                            script.async = true;
                            script.onload = () => {
                                if(!window.mermaid){ return; }
                                const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
                                window.mermaid.initialize({
                                    startOnLoad: false,
                                    securityLevel: 'loose', // Content is trusted (owned blog); allows links & styling
                                    theme: prefersDark ? 'dark' : 'default'
                                });
                                mermaidCodeBlocks.forEach((codeEl, i) => {
                                    // Avoid double-processing
                                    if(codeEl.dataset.mermaidProcessed){ return; }
                                    const pre = codeEl.closest('pre');
                                    if(!pre){ return; }
                                    const def = codeEl.textContent.trim(); // Use textContent to ignore highlight.js span wrappers
                                    const container = document.createElement('div');
                                    container.className = 'mermaid my-6';
                                    // Provide a deterministic ID (optional)
                                    container.id = 'mermaid-diagram-' + (i+1);
                                    container.textContent = def;
                                    pre.replaceWith(container);
                                    codeEl.dataset.mermaidProcessed = 'true';
                                });
                                try { window.mermaid.run && window.mermaid.run(); } catch(_) {}
                            };
                            document.head.appendChild(script);
                        }
                    } catch(e) {}
                })();
            </script>
</body>

</html>