{"version":"https://jsonfeed.org/version/1.1","title":"Stephen Ajulu","home_page_url":"https://ajulu.netlify.app/","feed_url":"https://ajulu.netlify.app/tags/hugo/feed.json","description":"Hello, I'm Stephen Ajulu, a seasoned multidisciplinary tech professional with over a decade of experience. I build impactful solutions using design, tech, and engineering in the pursuit of impact.","icon":"https://ajulu.netlify.app/images/me.jpg","authors":[{"name":"Stephen Ajulu","url":"https://stephenajulu.com","avatar":"https://ajulu.netlify.app/images/me.jpg"}],"items":[{"id":"https://ajulu.netlify.app/posts/indieweb-webmention-hugo-guide/","url":"https://ajulu.netlify.app/posts/indieweb-webmention-hugo-guide/","title":"Implementing Webmentions on Hugo: An IndieWeb Guide","summary":"Webmentions are a cornerstone of the IndieWeb movement. They allow you to \u0026ldquo;own your data\u0026rdquo; while still participating in the broader social web. Instead of having a centralized comment system (like Disqus), Webmentions allow you to receive notifications whenever someone links to your post from their own site, or even from social networks like Mastodon or Twitter.\nHow it Works The workflow typically follows the POSSE principle: Publish (on) Own Site, Syndicate Elsewhere.\n","content_html":"\u003cp\u003eWebmentions are a cornerstone of the IndieWeb movement. They allow you to \u0026ldquo;own your data\u0026rdquo; while still participating in the broader social web. Instead of having a centralized comment system (like Disqus), Webmentions allow you to receive notifications whenever someone links to your post from their own site, or even from social networks like Mastodon or Twitter.\u003c/p\u003e\n\u003ch2 id=\"how-it-works\"\u003eHow it Works\u003c/h2\u003e\n\u003cp\u003eThe workflow typically follows the \u003cstrong\u003ePOSSE\u003c/strong\u003e principle: \u003cstrong\u003eP\u003c/strong\u003eublish (on) \u003cstrong\u003eO\u003c/strong\u003ewn \u003cstrong\u003eS\u003c/strong\u003eite, \u003cstrong\u003eS\u003c/strong\u003eyndicate \u003cstrong\u003eE\u003c/strong\u003elsewhere.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eSyndication:\u003c/strong\u003e You publish a post here and share it on Mastodon or Twitter.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInteraction:\u003c/strong\u003e Someone likes or replies to your syndicated post on social media.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBridge:\u003c/strong\u003e A service like \u003ca href=\"https://bridgy.gy\"\u003eBridgy\u003c/a\u003e sees that interaction and sends a \u0026ldquo;Webmention\u0026rdquo; to your site\u0026rsquo;s endpoint.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eInbox:\u003c/strong\u003e Your site\u0026rsquo;s endpoint (configured via \u003ca href=\"https://webmention.io\"\u003eWebmention.io\u003c/a\u003e) receives the mention.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDisplay:\u003c/strong\u003e Your Hugo templates fetch these mentions via JavaScript and display them at the bottom of your post.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cdiv class=\"member-gate\" data-role=\"premium\"\u003e\n    \u003cdiv class=\"unlocked-content\" style=\"display:none;\"\u003e\n        \u003ch2 id=\"step-by-step-implementation\"\u003eStep-by-Step Implementation\u003c/h2\u003e\n\u003ch3 id=\"1-setup-your-identity-indieauth\"\u003e1. Setup your Identity (IndieAuth)\u003c/h3\u003e\n\u003cp\u003eEnsure your site has \u003ccode\u003erel=\u0026quot;me\u0026quot;\u003c/code\u003e links to your social profiles in the \u003ccode\u003e\u0026lt;head\u0026gt;\u003c/code\u003e. This proves you own the domain.\u003c/p\u003e\n\u003ch3 id=\"2-configure-webmentionio\"\u003e2. Configure Webmention.io\u003c/h3\u003e\n\u003cp\u003eLog in to Webmention.io with your domain. Add the following to your \u003ccode\u003eextend-head.html\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-html\" data-lang=\"html\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003elink\u003c/span\u003e \u003cspan class=\"na\"\u003erel\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;webmention\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ehref\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://webmention.io/ajulu.netlify.app/webmention\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003elink\u003c/span\u003e \u003cspan class=\"na\"\u003erel\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;pingback\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ehref\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://webmention.io/ajulu.netlify.app/xmlrpc\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch3 id=\"3-display-the-mentions\"\u003e3. Display the Mentions\u003c/h3\u003e\n\u003cp\u003eAdd a container and a script to your post template. We\u0026rsquo;ve already implemented a themed version of this in our \u003ccode\u003ewebmention.html\u003c/code\u003e partial, which uses CSS Grid to display likes and a threaded list for replies.\u003c/p\u003e\n\n    \u003c/div\u003e\n    \u003cdiv class=\"locked-message\"\u003e\n        \u003cdiv class=\"gate-overlay\"\u003e\n            \u003cspan class=\"gate-icon\"\u003e🔒\u003c/span\u003e\n            \u003ch3\u003ePremium Insight\u003c/h3\u003e\n            \u003cp\u003eThis deep-dive is exclusive to \u003cstrong\u003ePremium Members\u003c/strong\u003e.\u003c/p\u003e\n            \u003cdiv class=\"gate-actions\"\u003e\n                \u003ca href=\"/membership\" class=\"btn\"\u003eUnlock Access\u003c/a\u003e\n                \u003cbutton class=\"btn-secondary\" onclick=\"netlifyIdentity.open('login')\"\u003eAlready a member? Log in\u003c/button\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n\u003c/div\u003e\n\n\u003cscript\u003e\n    (function() {\n        function checkAccess() {\n            const user = window.netlifyIdentity \u0026\u0026 netlifyIdentity.currentUser();\n            const isPremium = user \u0026\u0026 user.app_metadata \u0026\u0026 user.app_metadata.roles \u0026\u0026 user.app_metadata.roles.includes('premium');\n            \n            document.querySelectorAll('.member-gate').forEach(gate =\u003e {\n                const unlocked = gate.querySelector('.unlocked-content');\n                const locked = gate.querySelector('.locked-message');\n                if (isPremium) {\n                    unlocked.style.display = 'block';\n                    locked.style.display = 'none';\n                } else {\n                    unlocked.style.display = 'none';\n                    locked.style.display = 'block';\n                }\n            });\n        }\n\n        if (window.netlifyIdentity) {\n            netlifyIdentity.on('init', checkAccess);\n            netlifyIdentity.on('login', checkAccess);\n            netlifyIdentity.on('logout', checkAccess);\n        }\n    })();\n\u003c/script\u003e\n\n\u003cstyle\u003e\n.locked-message {\n    padding: 40px;\n    background: var(--base-offset-color);\n    border-radius: 8px;\n    border: 1px dashed var(--highlight-color);\n    text-align: center;\n    margin: 30px 0;\n}\n\n.gate-icon {\n    font-size: 2rem;\n    display: block;\n    margin-bottom: 10px;\n}\n\n.gate-actions {\n    display: flex;\n    gap: 15px;\n    justify-content: center;\n    margin-top: 20px;\n}\n\n.btn-secondary {\n    background: transparent;\n    border: 1px solid var(--highlight-color);\n    color: var(--highlight-color);\n    padding: 10px 20px;\n    border-radius: 6px;\n    cursor: pointer;\n    font-weight: 600;\n}\n\u003c/style\u003e\n\n\u003ch2 id=\"why-bother\"\u003eWhy Bother?\u003c/h2\u003e\n\u003cp\u003eBy using Webmentions, you become a first-class citizen of the open web. You aren\u0026rsquo;t just a \u0026ldquo;user\u0026rdquo; on a platform; you are a node in a distributed social network.\u003c/p\u003e\n\u003cp\u003eHappy hacking!\u003c/p\u003e\n","date_published":"2026-04-27T10:00:00Z","image":"https://images.unsplash.com/photo-1516259762381-22954d7d3ad2?q=80\u0026w=1000\u0026auto=format\u0026fit=crop","tags":["indieweb","hugo","webmentions","posse"]},{"id":"https://ajulu.netlify.app/posts/image-optimization-demo/","url":"https://ajulu.netlify.app/posts/image-optimization-demo/","title":"How to Use Optimized Images","summary":"To keep your site fast and achieve high scores on Google PageSpeed Insights, you should use the new img shortcode for any local images you add to your posts.\nWhy use the img shortcode? WebP Support: It automatically generates a WebP version of your image, which is significantly smaller. Responsive Loading: It creates a srcset so mobile devices download smaller versions of the image. Lazy Loading: Images only load when they are about to enter the viewport. Automatic Dimensions: Prevents layout shifts (CLS) by adding width and height attributes. How to use it Instead of standard Markdown like ![Alt](path), use this:\n","content_html":"\u003cp\u003eTo keep your site fast and achieve high scores on Google PageSpeed Insights, you should use the new \u003ccode\u003eimg\u003c/code\u003e shortcode for any local images you add to your posts.\u003c/p\u003e\n\u003ch2 id=\"why-use-the-img-shortcode\"\u003eWhy use the \u003ccode\u003eimg\u003c/code\u003e shortcode?\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eWebP Support:\u003c/strong\u003e It automatically generates a WebP version of your image, which is significantly smaller.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eResponsive Loading:\u003c/strong\u003e It creates a \u003ccode\u003esrcset\u003c/code\u003e so mobile devices download smaller versions of the image.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLazy Loading:\u003c/strong\u003e Images only load when they are about to enter the viewport.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAutomatic Dimensions:\u003c/strong\u003e Prevents layout shifts (CLS) by adding \u003ccode\u003ewidth\u003c/code\u003e and \u003ccode\u003eheight\u003c/code\u003e attributes.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"how-to-use-it\"\u003eHow to use it\u003c/h2\u003e\n\u003cp\u003eInstead of standard Markdown like \u003ccode\u003e![Alt](path)\u003c/code\u003e, use this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\n\n\n\n  \n  \n  \n    \u003cimg src=\"/images/me.jpg\" alt=\"Stephen Ajulu\"  loading=\"lazy\"\u003e\n  \n\n\n\n\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003cem\u003eNote: The \u003ccode\u003esrc\u003c/code\u003e path should be relative to the \u003ccode\u003eassets/\u003c/code\u003e or \u003ccode\u003estatic/\u003c/code\u003e directory depending on where you store them, but it works best with images in \u003ccode\u003eassets/\u003c/code\u003e for full processing.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"example-in-action\"\u003eExample in Action\u003c/h2\u003e\n\n\n\n\n\n  \n  \n  \n    \u003cimg src=\"/images/me.jpg\" alt=\"Stephen Ajulu\" class=\"author-demo\" loading=\"lazy\"\u003e\n  \n\n\n\n\n\u003cp\u003eYou can also pass a custom CSS class:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\n\n\n\n  \n  \n  \n    \u003cimg src=\"/images/me.jpg\" alt=\"Alt text\" class=\"my-custom-class\" loading=\"lazy\"\u003e\n  \n\n\n\n\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e","date_published":"2026-04-25T10:00:00Z","image":"https://ajulu.netlify.app/images/me.jpg","tags":["tech","hugo","optimization"]}]}