[{"data":1,"prerenderedAt":916},["ShallowReactive",2],{"cms-blog-implementing-okta-authentication-in-a-nuxt-application":3,"cms-blog":20},{"title":4,"excerpt":5,"slug":8,"rawDate":9,"date":10,"modified":11,"content":12,"readingTime":13,"acf":14,"tags":17,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Implementing Okta authentication in a Nuxt application",{"raw":6,"plain":7},"\u003Cp>I was building an internal application using Nuxt 2, with a separate PHP API handling the backend. Unlike most websites I&#8217;d worked on, the application needed proper user authentication. Different users needed different levels of access, and the API couldn&#8217;t simply trust that somebody had logged into the frontend. I didn&#8217;t want to build an [&hellip;]\u003C\u002Fp>","I was building an internal application using Nuxt 2, with a separate PHP API handling the backend. Unlike most websites I’d worked on, the application needed proper user authentication. Different users needed different levels of access, and the API couldn’t simply trust that somebody had logged into the frontend. I didn’t want to build an […]","implementing-okta-authentication-in-a-nuxt-application","2023-03-19T16:34:00",["Date","2023-03-19T00:00:00.000Z"],["Date","2026-11-13T00:00:00.000Z"],"\u003Cp class=\"wp-block-paragraph\">I was building an internal application using Nuxt 2, with a separate PHP API handling the backend.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Unlike most websites I’d worked on, the application needed proper user authentication. Different users needed different levels of access, and the API couldn’t simply trust that somebody had logged into the frontend.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I didn’t want to build an authentication system from scratch, particularly when it came to managing passwords, multi-factor authentication and sessions.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I decided to use \u003Ca href=\"https:\u002F\u002Fwww.okta.com\u002F\" target=\"_blank\">Okta\u003C\u002Fa>, which provides identity management and authentication using OpenID Connect and OAuth 2.0.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Getting users logged in was fairly straightforward. Keeping everything authenticated as tokens expired turned out to be much more interesting.\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">Setting up Okta in Nuxt\u003C\u002Fh2>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The application used Nuxt 2 and Vue 2, with client-side rendering.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I used the \u003Ccode>@okta\u002Fokta-auth-js\u003C\u002Fcode> and \u003Ccode>@okta\u002Fokta-vue\u003C\u002Fcode> packages to integrate Okta with the application.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The configuration lived in a Nuxt plugin.\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"language-javascript torchlight\" data-language=\"javascript\">\u003Ccode data-torchlight-id=\"67a69678-2a31-06e1-13d6-db6ba3ef7a72\">\u003C!-- Syntax highlighted by torchlight.dev -->\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 1\u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">import\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">Vue\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">from\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'vue'\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 2\u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">import\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">OktaVue\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">from\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'@okta\u002Fokta-vue'\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 3\u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">import\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> { \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">OktaAuth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> } \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">from\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'@okta\u002Fokta-auth-js'\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 4\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 5\u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">export\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">default\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> (\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">context\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">, \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">inject\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">=&gt;\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 6\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">oktaAuth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">new\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">OktaAuth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">({\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 7\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">issuer:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">context\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$config\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">OKTA_ISSUER\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> + \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'\u002Foauth2\u002Fdefault'\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">,\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 8\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">clientId:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">context\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$config\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">OKTA_CLIENT_ID\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">,\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 9\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">redirectUri:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">window\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">location\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">origin\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> + \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'\u002Fimplicit\u002Fcallback'\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">,\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">10\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">scopes:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> [\u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'openid'\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">, \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'profile'\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">, \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'email'\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">],\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">11\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">pkce:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">true\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">12\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  })\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">13\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">14\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">oktaAuth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">start\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">()\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">15\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">16\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">Vue\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">use\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">OktaVue\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">, { \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">oktaAuth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> })\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">17\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">18\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">inject\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'auth'\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">, \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">oktaAuth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">19\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fcode>\n    \u003Cdiv class=\"copy-button__container js-copy-to-clipboard-container\">\n      \u003Cdiv class=\"copy-button__text js-copy-to-clipboard-notification\">Copied!\u003C\u002Fdiv>\n      \u003Cbutton type=\"button\" title=\"Copy to clipboard\" class=\"copy-button__button js-copy-to-clipboard-button\">\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n    \u003Cspan class=\"torchlight-copy-target js-copy-to-clipboard-target\" style=\"display: none\">import Vue from 'vue'\nimport OktaVue from '@okta\u002Fokta-vue'\nimport { OktaAuth } from '@okta\u002Fokta-auth-js'\n\nexport default (context, inject) =&gt; {\n  const oktaAuth = new OktaAuth({\n    issuer: context.$config.OKTA_ISSUER + '\u002Foauth2\u002Fdefault',\n    clientId: context.$config.OKTA_CLIENT_ID,\n    redirectUri: window.location.origin + '\u002Fimplicit\u002Fcallback',\n    scopes: ['openid', 'profile', 'email'],\n    pkce: true\n  })\n\n  oktaAuth.start()\n\n  Vue.use(OktaVue, { oktaAuth })\n\n  inject('auth', oktaAuth)\n}\u003C\u002Fspan>\n    \u003C\u002Fpre>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The issuer identifies the Okta authorisation server, while the client ID identifies the application.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Both can be configured through Nuxt’s runtime configuration.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The important setting here is \u003Ccode>pkce: true\u003C\u002Fcode>.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">PKCE, or Proof Key for Code Exchange, is designed for applications which can’t securely store a client secret, such as a JavaScript application running in the browser.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">It adds protection to the authorisation code flow without requiring a secret to be embedded in the frontend.\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">Creating the login page\u003C\u002Fh2>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Rather than building the entire login form myself, I used Okta’s Sign-In Widget.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The widget handles the authentication interface, including the parts which would otherwise require a lot of additional code.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I loaded it dynamically on the login page.\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"language-javascript torchlight\" data-language=\"javascript\">\u003Ccode data-torchlight-id=\"cde741e9-7d65-6f2d-0233-9f93af48baed\">\u003C!-- Syntax highlighted by torchlight.dev -->\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 1\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> { \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">default\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">: \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">OktaSignIn\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> } = \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">await\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">import\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 2\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'@okta\u002Fokta-signin-widget'\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 3\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 4\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 5\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">widget\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">new\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">OktaSignIn\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">({\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 6\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">baseUrl:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">this\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$config\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">OKTA_ISSUER\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">,\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 7\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">clientId:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">this\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$config\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">OKTA_CLIENT_ID\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">,\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 8\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">redirectUri:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">window\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">location\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">origin\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> + \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'\u002Fimplicit\u002Fcallback'\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">,\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 9\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">authClient:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">this\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$auth\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">10\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">})\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">11\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">12\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">widget\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">showSignInToGetTokens\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">({\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">13\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">el:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'#okta-signin-container'\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">14\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">}).\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">then\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">async\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> (\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">tokens\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">=&gt;\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">15\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">await\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">this\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$auth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">tokenManager\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">setTokens\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">tokens\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">16\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">widget\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">remove\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">()\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">17\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">})\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fcode>\n    \u003Cdiv class=\"copy-button__container js-copy-to-clipboard-container\">\n      \u003Cdiv class=\"copy-button__text js-copy-to-clipboard-notification\">Copied!\u003C\u002Fdiv>\n      \u003Cbutton type=\"button\" title=\"Copy to clipboard\" class=\"copy-button__button js-copy-to-clipboard-button\">\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n    \u003Cspan class=\"torchlight-copy-target js-copy-to-clipboard-target\" style=\"display: none\">const { default: OktaSignIn } = await import(\n  '@okta\u002Fokta-signin-widget'\n)\n\nconst widget = new OktaSignIn({\n  baseUrl: this.$config.OKTA_ISSUER,\n  clientId: this.$config.OKTA_CLIENT_ID,\n  redirectUri: window.location.origin + '\u002Fimplicit\u002Fcallback',\n  authClient: this.$auth\n})\n\nwidget.showSignInToGetTokens({\n  el: '#okta-signin-container'\n}).then(async (tokens) =&gt; {\n  await this.$auth.tokenManager.setTokens(tokens)\n  widget.remove()\n})\u003C\u002Fspan>\n    \u003C\u002Fpre>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Once the user authenticated, the tokens were stored by Okta’s token manager.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I could then load the user information and the data needed by the application.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">This also meant I didn’t need to handle passwords directly in my own code.\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">Authenticating API requests\u003C\u002Fh2>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The frontend communicated with a separate API, using Axios.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Logging into Okta wasn’t enough on its own. Every API request also needed to include an access token so the backend could verify who was making the request.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Rather than manually adding the token every time I called the API, I created an Axios interceptor.\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"language-javascript torchlight\" data-language=\"javascript\">\u003Ccode data-torchlight-id=\"97270840-55f6-90a0-09b6-5b837964dfac\">\u003C!-- Syntax highlighted by torchlight.dev -->\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">1\u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">export\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">default\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">function\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> ({ \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$axios\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">, \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$auth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> }) {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">2\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$axios\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">interceptors\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">request\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">use\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">async\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> (\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">config\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">=&gt;\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">3\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">token\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">await\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">$auth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">getAccessToken\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">()\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">4\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">5\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">config\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">headers\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">Authorization\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">`Bearer \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">${\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">token\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">}\u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">`\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">6\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">7\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">return\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">config\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">8\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  })\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">9\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fcode>\n    \u003Cdiv class=\"copy-button__container js-copy-to-clipboard-container\">\n      \u003Cdiv class=\"copy-button__text js-copy-to-clipboard-notification\">Copied!\u003C\u002Fdiv>\n      \u003Cbutton type=\"button\" title=\"Copy to clipboard\" class=\"copy-button__button js-copy-to-clipboard-button\">\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n    \u003Cspan class=\"torchlight-copy-target js-copy-to-clipboard-target\" style=\"display: none\">export default function ({ $axios, $auth }) {\n  $axios.interceptors.request.use(async (config) =&gt; {\n    const token = await $auth.getAccessToken()\n\n    config.headers.Authorization = `Bearer ${token}`\n\n    return config\n  })\n}\u003C\u002Fspan>\n    \u003C\u002Fpre>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Now, whenever the application made a request, the interceptor retrieved the current access token and attached it to the \u003Ccode>Authorization\u003C\u002Fcode> header.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The API could then validate the token and decide whether the user had permission to access the requested resource.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I also used the user’s group information to control access to parts of the frontend.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Of course, hiding something in the UI isn’t security. The API still needs to perform its own authorisation checks.\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">The problem with expiring access tokens\u003C\u002Fh2>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Access tokens don’t last forever.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">That’s a good thing for security, but it creates an interesting problem in a single-page application.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">A user might log in, open the dashboard and leave it running for several hours.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Eventually, their access token expires.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">If the application tries to make another API request using that expired token, the request fails.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Okta provides automatic token renewal, but I ended up taking more control over the process because I wanted to coordinate renewal with the rest of the application.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I needed to know when tokens were being renewed so that other requests could wait rather than being sent with outdated credentials.\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">Renewing tokens before they expire\u003C\u002Fh2>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I disabled Okta’s automatic renewal and added my own scheduling logic.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The access token contains an expiry timestamp, which can be used to work out when it needs renewing.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I scheduled renewal thirty seconds before the token expired.\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"language-javascript torchlight\" data-language=\"javascript\">\u003Ccode data-torchlight-id=\"9fc62432-655b-0970-1f75-f259fbf7d3a1\">\u003C!-- Syntax highlighted by torchlight.dev -->\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 1\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">accessToken\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">await\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">auth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">tokenManager\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">get\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 2\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #CE9178;\">'accessToken'\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 3\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 4\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 5\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">expiresAt\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">accessToken\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">expiresAt\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> * \u003C\u002Fspan>\u003Cspan style=\"color: #B5CEA8;\">1000\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 6\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">now\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #4EC9B0;\">Date\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">now\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">()\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 7\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 8\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">delay\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">expiresAt\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> - \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">now\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> - \u003C\u002Fspan>\u003Cspan style=\"color: #B5CEA8;\">30000\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 9\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">10\u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">if\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> (\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">delay\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> &lt;= \u003C\u002Fspan>\u003Cspan style=\"color: #B5CEA8;\">0\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">11\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">await\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">renewTokens\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">()\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">12\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">} \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">else\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">13\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">setTimeout\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">renewTokens\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">, \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">delay\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">14\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fcode>\n    \u003Cdiv class=\"copy-button__container js-copy-to-clipboard-container\">\n      \u003Cdiv class=\"copy-button__text js-copy-to-clipboard-notification\">Copied!\u003C\u002Fdiv>\n      \u003Cbutton type=\"button\" title=\"Copy to clipboard\" class=\"copy-button__button js-copy-to-clipboard-button\">\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n    \u003Cspan class=\"torchlight-copy-target js-copy-to-clipboard-target\" style=\"display: none\">const accessToken = await auth.tokenManager.get(\n  'accessToken'\n)\n\nconst expiresAt = accessToken.expiresAt * 1000\nconst now = Date.now()\n\nconst delay = expiresAt - now - 30000\n\nif (delay &lt;= 0) {\n  await renewTokens()\n} else {\n  setTimeout(renewTokens, delay)\n}\u003C\u002Fspan>\n    \u003C\u002Fpre>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">When the timer fired, the application requested new tokens and stored them using Okta’s token manager.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I also kept a flag in Vuex indicating whether token renewal was in progress.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">That turned out to be useful for the next problem.\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">Pausing API requests during renewal\u003C\u002Fh2>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Imagine an access token is about to expire.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The application starts renewing it, but at the same time several components make API requests.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Without any coordination, those requests might be sent with the old token before renewal finishes.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I added another Axios interceptor which checks whether token renewal is in progress.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">If it is, the request waits.\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"language-javascript torchlight\" data-language=\"javascript\">\u003Ccode data-torchlight-id=\"57263267-6348-3d15-7ff8-90a264384b99\">\u003C!-- Syntax highlighted by torchlight.dev -->\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 1\u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">function\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">pauseWhilstRenewing\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">config\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">, \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">store\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 2\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">if\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> (!\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">store\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">state\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">isRenewingTokens\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 3\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">return\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">config\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 4\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  }\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 5\u003C\u002Fspan>&nbsp;\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 6\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">return\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">new\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4EC9B0;\">Promise\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">((\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">resolve\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">=&gt;\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 7\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">const\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #4FC1FF;\">unwatch\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> = \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">store\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">watch\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 8\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">      \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">state\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">=&gt;\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">state\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">isRenewingTokens\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">,\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\"> 9\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">      (\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">renewing\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) \u003C\u002Fspan>\u003Cspan style=\"color: #569CD6;\">=&gt;\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">10\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">        \u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">if\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> (!\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">renewing\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">) {\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">11\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">          \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">unwatch\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">()\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">12\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">          \u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">resolve\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">(\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">config\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">13\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">        }\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">14\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">      }\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">15\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">    )\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">16\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  })\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">17\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fcode>\n    \u003Cdiv class=\"copy-button__container js-copy-to-clipboard-container\">\n      \u003Cdiv class=\"copy-button__text js-copy-to-clipboard-notification\">Copied!\u003C\u002Fdiv>\n      \u003Cbutton type=\"button\" title=\"Copy to clipboard\" class=\"copy-button__button js-copy-to-clipboard-button\">\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n    \u003Cspan class=\"torchlight-copy-target js-copy-to-clipboard-target\" style=\"display: none\">function pauseWhilstRenewing(config, store) {\n  if (!store.state.isRenewingTokens) {\n    return config\n  }\n\n  return new Promise((resolve) =&gt; {\n    const unwatch = store.watch(\n      state =&gt; state.isRenewingTokens,\n      (renewing) =&gt; {\n        if (!renewing) {\n          unwatch()\n          resolve(config)\n        }\n      }\n    )\n  })\n}\u003C\u002Fspan>\n    \u003C\u002Fpre>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Once the renewal finishes, the waiting requests can continue.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">This works alongside the interceptor responsible for attaching the access token.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The order of those interceptors matters. Requests need to wait until renewal has completed before the new token is attached.\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">Handling logout\u003C\u002Fh2>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">I also added a logout function which signed the user out of Okta and cleared locally stored application data.\u003C\u002Fp>\n\n\n\n\u003Cpre class=\"language-javascript torchlight\" data-language=\"javascript\">\u003Ccode data-torchlight-id=\"eb76216a-61da-3b89-7d2c-eb0d1be42dc1\">\u003C!-- Syntax highlighted by torchlight.dev -->\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">1\u003C\u002Fspan>\u003Cspan style=\"color: #C586C0;\">await\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">auth\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #DCDCAA;\">signOut\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">({\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">2\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">  \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">postLogoutRedirectUri:\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\"> \u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">window\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">location\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">.\u003C\u002Fspan>\u003Cspan style=\"color: #9CDCFE;\">origin\u003C\u002Fspan>\u003C\u002Fdiv>\u003Cdiv class=\"line\">\u003Cspan style=\"color:#858585; text-align: right; -webkit-user-select: none; user-select: none;\" class=\"line-number\">3\u003C\u002Fspan>\u003Cspan style=\"color: #D4D4D4;\">})\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fcode>\n    \u003Cdiv class=\"copy-button__container js-copy-to-clipboard-container\">\n      \u003Cdiv class=\"copy-button__text js-copy-to-clipboard-notification\">Copied!\u003C\u002Fdiv>\n      \u003Cbutton type=\"button\" title=\"Copy to clipboard\" class=\"copy-button__button js-copy-to-clipboard-button\">\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n    \u003Cspan class=\"torchlight-copy-target js-copy-to-clipboard-target\" style=\"display: none\">await auth.signOut({\n  postLogoutRedirectUri: window.location.origin\n})\u003C\u002Fspan>\n    \u003C\u002Fpre>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The application then returned to its unauthenticated state.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">It’s important to clear application state as well as ending the identity provider’s session, particularly if the frontend has cached information about the previous user.\u003C\u002Fp>\n\n\n\n\u003Ch2 class=\"wp-block-heading\">What I learned\u003C\u002Fh2>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">The main thing I took away from this project is that authentication isn’t just about checking a username and password.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">A real application needs to consider token expiry, API authorisation, refreshing sessions, multiple simultaneous requests, long-running connections and logout behaviour.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">Using an identity provider removes the need to implement the most sensitive parts of authentication yourself, but you still need to integrate it properly with the rest of your application.\u003C\u002Fp>\n\n\n\n\u003Cp class=\"wp-block-paragraph\">And in a single-page application, keeping everything authenticated over several hours can be more complicated than getting somebody logged in in the first place.\u003C\u002Fp>",5,{"project_url":15,"github":15,"icons":16},"",[],[],"Joe",null,[21,138,247,357,473,597,704,809],[22,34,46,58,70,81,92,104,115,127],{"title":23,"excerpt":24,"slug":27,"date":28,"modified":29,"readingTime":30,"acf":31,"tags":33,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"How AI helped me add my Mitsubishi Delica D:5 to UK insurance databases",{"raw":25,"plain":26},"\u003Cp>Earlier this year I bought a 2007 Mitsubishi Delica D:5. The D:5 has been sold in Japan since 2007 but was never officially sold in the UK. Mine is a Japanese import, which means that while it is perfectly legal and registered with the DVLA, getting insurance for it can be a little more complicated [&hellip;]\u003C\u002Fp>","Earlier this year I bought a 2007 Mitsubishi Delica D:5. The D:5 has been sold in Japan since 2007 but was never officially sold in the UK. Mine is a Japanese import, which means that while it is perfectly legal and registered with the DVLA, getting insurance for it can be a little more complicated […]","how-ai-helped-me-add-my-mitsubishi-delica-d5-to-uk-insurance-databases",["Date","2026-11-06T00:00:00.000Z"],["Date","2026-11-06T00:00:00.000Z"],8,{"project_url":15,"github":15,"icons":32},[],[],{"title":35,"excerpt":36,"slug":39,"date":40,"modified":41,"readingTime":42,"acf":43,"tags":45,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Moving my site from Netlify to Cloudflare Workers",{"raw":37,"plain":38},"\u003Cp>I moved this site&#8217;s builds, hosting, forms and images from Netlify to Cloudflare Workers, and upgraded it to Nuxt 4 and TypeScript along the way. Here&#8217;s what moved where, and what caught me out.\u003C\u002Fp>","I moved this site’s builds, hosting, forms and images from Netlify to Cloudflare Workers, and upgraded it to Nuxt 4 and TypeScript along the way. Here’s what moved where, and what caught me out.","moving-from-netlify-to-cloudflare-workers",["Date","2026-10-27T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],2,{"project_url":15,"github":15,"icons":44},[],[],{"title":47,"excerpt":48,"slug":51,"date":52,"modified":53,"readingTime":54,"acf":55,"tags":57,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Replacing a BI tool that couldn’t handle 40 million rows",{"raw":49,"plain":50},"\u003Cp>Our reporting went from stale database syncs, to MySQL views, to a BI tool that copied everything and choked on our biggest table. Querying the database directly with Grafana cut dashboard updates from two hours to near-instant.\u003C\u002Fp>","Our reporting went from stale database syncs, to MySQL views, to a BI tool that copied everything and choked on our biggest table. Querying the database directly with Grafana cut dashboard updates from two hours to near-instant.","replacing-gogrow-with-grafana",["Date","2026-09-29T00:00:00.000Z"],["Date","2026-11-10T00:00:00.000Z"],3,{"project_url":15,"github":15,"icons":56},[],[],{"title":59,"excerpt":60,"slug":63,"date":64,"modified":65,"readingTime":66,"acf":67,"tags":69,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Scaling ECS on PHP-FPM workers instead of CPU and memory",{"raw":61,"plain":62},"\u003Cp>Our ECS services scaled on CPU and memory, which meant traffic spikes took the platform down before a new container ever started. Scaling on busy PHP-FPM workers fixed the outages and let us run 3 containers instead of 8.\u003C\u002Fp>","Our ECS services scaled on CPU and memory, which meant traffic spikes took the platform down before a new container ever started. Scaling on busy PHP-FPM workers fixed the outages and let us run 3 containers instead of 8.","scaling-ecs-on-php-fpm-workers",["Date","2026-09-15T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],9,{"project_url":15,"github":15,"icons":68},[],[],{"title":71,"excerpt":72,"slug":75,"date":76,"modified":77,"readingTime":30,"acf":78,"tags":80,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Running our PHPUnit suite on one EC2 runner instead of 40 GitHub runners",{"raw":73,"plain":74},"\u003Cp>Our PHPUnit suite was split across a 40-job matrix of GitHub-hosted runners. Moving it onto a single self-hosted EC2 runner simplified the pipeline and saves us over $200 a month.\u003C\u002Fp>","Our PHPUnit suite was split across a 40-job matrix of GitHub-hosted runners. Moving it onto a single self-hosted EC2 runner simplified the pipeline and saves us over $200 a month.","phpunit-on-a-self-hosted-ec2-runner",["Date","2026-09-01T00:00:00.000Z"],["Date","2026-11-10T00:00:00.000Z"],{"project_url":15,"github":15,"icons":79},[],[],{"title":82,"excerpt":83,"slug":86,"date":87,"modified":88,"readingTime":30,"acf":89,"tags":91,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Replacing regex with Amazon Bedrock to read clinical forms",{"raw":84,"plain":85},"\u003Cp>Practitioners were re-typing patient details that GPs had already written down. Replacing a brittle regex parser with an Amazon Bedrock PDF reader cut the time to add a patient by 40%.\u003C\u002Fp>","Practitioners were re-typing patient details that GPs had already written down. Replacing a brittle regex parser with an Amazon Bedrock PDF reader cut the time to add a patient by 40%.","amazon-bedrock-clinical-forms",["Date","2026-08-18T00:00:00.000Z"],["Date","2026-11-13T00:00:00.000Z"],{"project_url":15,"github":15,"icons":90},[],[],{"title":93,"excerpt":94,"slug":97,"date":98,"modified":99,"readingTime":100,"acf":101,"tags":103,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Stopping booking reminders being dropped with Symfony Messenger",{"raw":95,"plain":96},"\u003Cp>A cron job that sent booking reminder emails kept timing out on large batches, silently dropping messages patients needed. Moving sending onto a Symfony Messenger queue fixed it, and made our API responses 30% faster too.\u003C\u002Fp>","A cron job that sent booking reminder emails kept timing out on large batches, silently dropping messages patients needed. Moving sending onto a Symfony Messenger queue fixed it, and made our API responses 30% faster too.","booking-reminders-symfony-messenger",["Date","2026-08-04T00:00:00.000Z"],["Date","2026-11-13T00:00:00.000Z"],6,{"project_url":15,"github":15,"icons":102},[],[],{"title":105,"excerpt":106,"slug":109,"date":110,"modified":111,"readingTime":100,"acf":112,"tags":114,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Upgrading a live Symfony platform from PHP 7.4 to 8.4",{"raw":107,"plain":108},"\u003Cp>How we moved a busy Symfony platform from PHP 7.4 to 8.4 without disrupting the health services that depend on it, and what we got out of it.\u003C\u002Fp>","How we moved a busy Symfony platform from PHP 7.4 to 8.4 without disrupting the health services that depend on it, and what we got out of it.","upgrading-symfony-from-php-7-4-to-8-4",["Date","2026-07-21T00:00:00.000Z"],["Date","2026-11-13T00:00:00.000Z"],{"project_url":15,"github":15,"icons":113},[],[],{"title":116,"excerpt":117,"slug":120,"date":121,"modified":122,"readingTime":123,"acf":124,"tags":126,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Standardising ESLint and Stylelint across multiple projects",{"raw":118,"plain":119},"\u003Cp>I&#8217;ve worked on quite a few JavaScript and Vue projects over the years, and one thing that started to annoy me was maintaining the same linting configuration across all of them. Every project had its own ESLint and Stylelint configuration. Most of the rules were identical, but whenever I wanted to change something, I had [&hellip;]\u003C\u002Fp>","I’ve worked on quite a few JavaScript and Vue projects over the years, and one thing that started to annoy me was maintaining the same linting configuration across all of them. Every project had its own ESLint and Stylelint configuration. Most of the rules were identical, but whenever I wanted to change something, I had […]","standardising-eslint-and-stylelint-across-multiple-projects",["Date","2025-09-24T00:00:00.000Z"],["Date","2026-11-11T00:00:00.000Z"],7,{"project_url":15,"github":15,"icons":125},[],[],{"title":128,"excerpt":129,"slug":132,"date":133,"modified":134,"readingTime":123,"acf":135,"tags":137,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Running Nuxt locally with Cloudflare bindings",{"raw":130,"plain":131},"\u003Cp>I was building an application using Nuxt, with Cloudflare Workers handling the backend and D1 providing the database. Everything worked reasonably well when deployed to Cloudflare, but local development was a different story. Normally, I can run pnpm dev and Nuxt will start a development server, complete with hot module replacement. The problem was that [&hellip;]\u003C\u002Fp>","I was building an application using Nuxt, with Cloudflare Workers handling the backend and D1 providing the database. Everything worked reasonably well when deployed to Cloudflare, but local development was a different story. Normally, I can run pnpm dev and Nuxt will start a development server, complete with hot module replacement. The problem was that […]","running-nuxt-locally-with-cloudflare-bindings",["Date","2025-06-07T00:00:00.000Z"],["Date","2026-11-11T00:00:00.000Z"],{"project_url":15,"github":15,"icons":136},[],[],[139,150,161,172,183,194,205,215,226,237],{"title":140,"excerpt":141,"slug":144,"date":145,"modified":146,"readingTime":147,"acf":148,"tags":149,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"How to download a Google Sheet on a cron",{"raw":142,"plain":143},"\u003Cp>Working with Google Cloud can be confusing for beginners and sometimes all you need is to manipulate a Google Sheet. I have a couple of use cases for downloading a sheet and uploading a sheet. I haven&#8217;t quite figured out the upload part yet, but I have got downloading to work. I&#8217;ve decided to write [&hellip;]\u003C\u002Fp>","Working with Google Cloud can be confusing for beginners and sometimes all you need is to manipulate a Google Sheet. I have a couple of use cases for downloading a sheet and uploading a sheet. I haven’t quite figured out the upload part yet, but I have got downloading to work. I’ve decided to write […]","how-to-download-a-google-sheet-on-a-cron",["Date","2024-02-22T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],4,{"project_url":15,"github":15,"icons":15},[],{"title":151,"excerpt":152,"slug":155,"date":156,"modified":157,"readingTime":158,"acf":159,"tags":160,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"How to add Custom Variables to LiveChat",{"raw":153,"plain":154},"\u003Cp>I had a task to add a Google Analytics ID to the user profile in LiveChat. We wanted to be able to match up the person we were chatting to with the analytics data in Google. This looked really simple according to their docs. You can just use the following function: What their docs failed [&hellip;]\u003C\u002Fp>","I had a task to add a Google Analytics ID to the user profile in LiveChat. We wanted to be able to match up the person we were chatting to with the analytics data in Google. This looked really simple according to their docs. You can just use the following function: What their docs failed […]","how-to-add-custom-variables-to-livechat",["Date","2024-01-16T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],1,{"project_url":15,"github":15,"icons":15},[],{"title":162,"excerpt":163,"slug":166,"date":167,"modified":168,"readingTime":123,"acf":169,"tags":171,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Using Cloudflare D1 and Auth.js with Nuxt",{"raw":164,"plain":165},"\u003Cp>I built a small application to keep track of the TV shows I&#8217;m watching. Before that, I was keeping a list in a Word document, manually checking IMDb to find out whether programmes had been renewed or cancelled, and adding upcoming episodes to my calendar. I eventually decided to automate it. The application was built [&hellip;]\u003C\u002Fp>","I built a small application to keep track of the TV shows I’m watching. Before that, I was keeping a list in a Word document, manually checking IMDb to find out whether programmes had been renewed or cancelled, and adding upcoming episodes to my calendar. I eventually decided to automate it. The application was built […]","using-cloudflare-d1-and-auth-js-with-nuxt",["Date","2024-01-05T00:00:00.000Z"],["Date","2026-11-11T00:00:00.000Z"],{"project_url":15,"github":15,"icons":170},[],[],{"title":173,"excerpt":174,"slug":177,"date":178,"modified":179,"readingTime":123,"acf":180,"tags":182,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Moving Gatsby Image Processing to Netlify Image CDN",{"raw":175,"plain":176},"\u003Cp>With Netlify&#8217;s acquisition of Gatsby and subsequent shutdown of Gatsby Cloud, it left many users racing to migrate their deployment pipeline to Netlify. Netlify did do well to implement some of Gatsby Cloud&#8217;s features, but the overall experience feels half-baked. Many of the docs are incomplete, or haven&#8217;t been updated in a couple of years. [&hellip;]\u003C\u002Fp>","With Netlify’s acquisition of Gatsby and subsequent shutdown of Gatsby Cloud, it left many users racing to migrate their deployment pipeline to Netlify. Netlify did do well to implement some of Gatsby Cloud’s features, but the overall experience feels half-baked. Many of the docs are incomplete, or haven’t been updated in a couple of years. […]","moving-gatsby-image-processing-to-netlify-image-cdn",["Date","2023-12-19T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],{"project_url":15,"github":15,"icons":181},[],[],{"title":184,"excerpt":185,"slug":188,"date":189,"modified":190,"readingTime":147,"acf":191,"tags":193,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Creating a reusable date component for Nuxt",{"raw":186,"plain":187},"\u003Cp>When I was building my personal website with Nuxt, I wanted something a bit more interesting than a plain text date next to each blog post. I ended up creating a small date component which displays the day in a coloured block, with the month and year underneath in a ribbon shape. It&#8217;s a fairly [&hellip;]\u003C\u002Fp>","When I was building my personal website with Nuxt, I wanted something a bit more interesting than a plain text date next to each blog post. I ended up creating a small date component which displays the day in a coloured block, with the month and year underneath in a ribbon shape. It’s a fairly […]","creating-a-reusable-date-component-for-nuxt",["Date","2023-11-20T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],{"project_url":15,"github":15,"icons":192},[],[],{"title":195,"excerpt":196,"slug":199,"date":200,"modified":201,"readingTime":100,"acf":202,"tags":204,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Creating a custom FAQ block for WordPress Gutenberg",{"raw":197,"plain":198},"\u003Cp>I was working on a WordPress theme and wanted a reusable FAQ section that could be added to pages using the block editor. There are plenty of FAQ plugins available, but I didn&#8217;t particularly want to install another plugin just to display a few questions and answers. I also wanted more control over how the [&hellip;]\u003C\u002Fp>","I was working on a WordPress theme and wanted a reusable FAQ section that could be added to pages using the block editor. There are plenty of FAQ plugins available, but I didn’t particularly want to install another plugin just to display a few questions and answers. I also wanted more control over how the […]","creating-a-custom-faq-block-for-wordpress-gutenberg",["Date","2023-10-16T00:00:00.000Z"],["Date","2026-11-13T00:00:00.000Z"],{"project_url":15,"github":15,"icons":203},[],[],{"title":206,"excerpt":207,"slug":210,"date":211,"modified":212,"readingTime":42,"acf":213,"tags":214,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"This one Nuxt trick prevents broken deploys",{"raw":208,"plain":209},"\u003Cp>Have you ever experienced broken deploys while using Nuxt? It can be frustrating when your build encounters errors, but still proceeds to deploy all the files, resulting in broken pages on your website. Fortunately, I discovered a handy Nuxt trick that prevents such broken deploys and ensures a more reliable deployment process. In my case, [&hellip;]\u003C\u002Fp>","Have you ever experienced broken deploys while using Nuxt? It can be frustrating when your build encounters errors, but still proceeds to deploy all the files, resulting in broken pages on your website. Fortunately, I discovered a handy Nuxt trick that prevents such broken deploys and ensures a more reliable deployment process. In my case, […]","this-one-nuxt-trick-prevents-broken-deploys",["Date","2023-07-12T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":216,"excerpt":217,"slug":220,"date":221,"modified":222,"readingTime":100,"acf":223,"tags":225,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Understanding the different caching layers in WordPress",{"raw":218,"plain":219},"\u003Cp>Caching is one of the easiest ways to improve the performance of a WordPress website, but it can also become unnecessarily complicated. You install a caching plugin, enable a few settings, and suddenly everything is supposed to be faster. The problem is that not all caches do the same thing. On the WordPress servers I&#8217;ve [&hellip;]\u003C\u002Fp>","Caching is one of the easiest ways to improve the performance of a WordPress website, but it can also become unnecessarily complicated. You install a caching plugin, enable a few settings, and suddenly everything is supposed to be faster. The problem is that not all caches do the same thing. On the WordPress servers I’ve […]","understanding-the-different-caching-layers-in-wordpress",["Date","2023-06-21T00:00:00.000Z"],["Date","2026-11-10T00:00:00.000Z"],{"project_url":15,"github":15,"icons":224},[],[],{"title":227,"excerpt":228,"slug":231,"date":232,"modified":233,"readingTime":147,"acf":234,"tags":236,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Managing Docker logs with logrotate",{"raw":229,"plain":230},"\u003Cp>One of the less interesting parts of running your own server is dealing with logs. They&#8217;re useful when something goes wrong, but most of the time you don&#8217;t need to keep months of access logs, error logs and application output. If they&#8217;re left alone, they can gradually consume a surprising amount of disk space. I [&hellip;]\u003C\u002Fp>","One of the less interesting parts of running your own server is dealing with logs. They’re useful when something goes wrong, but most of the time you don’t need to keep months of access logs, error logs and application output. If they’re left alone, they can gradually consume a surprising amount of disk space. I […]","managing-docker-logs-with-logrotate",["Date","2023-06-20T00:00:00.000Z"],["Date","2026-11-10T00:00:00.000Z"],{"project_url":15,"github":15,"icons":235},[],[],{"title":238,"excerpt":239,"slug":242,"date":243,"modified":244,"readingTime":54,"acf":245,"tags":246,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"The importance of read time for blog posts and how to automatically generate it using Node.js",{"raw":240,"plain":241},"\u003Cp>In today&#8217;s fast-paced digital world, attention spans are getting shorter, and users often have limited time to consume content. As a result, providing readers with an estimated reading time can be a valuable feature for any blog or content-based website. Why having a read time for a blog post is important Automatically generate a read [&hellip;]\u003C\u002Fp>","In today’s fast-paced digital world, attention spans are getting shorter, and users often have limited time to consume content. As a result, providing readers with an estimated reading time can be a valuable feature for any blog or content-based website. Why having a read time for a blog post is important Automatically generate a read […]","the-importance-of-read-time-for-blog-posts-and-how-to-automatically-generate-it-using-node-js",["Date","2023-06-20T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],[248,259,271,283,296,307,318,328,335,346],{"title":249,"excerpt":250,"slug":253,"date":254,"modified":255,"readingTime":13,"acf":256,"tags":258,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Using Server-Sent Events and Redis to keep a Vue application updated",{"raw":251,"plain":252},"\u003Cp>I was building a project management application using Nuxt and a PHP API. The application had a dashboard showing multiple projects, each with its own information, tasks and status. The problem was keeping everything up to date. If I had the application open on two computers and updated a project on one, the other wouldn&#8217;t [&hellip;]\u003C\u002Fp>","I was building a project management application using Nuxt and a PHP API. The application had a dashboard showing multiple projects, each with its own information, tasks and status. The problem was keeping everything up to date. If I had the application open on two computers and updated a project on one, the other wouldn’t […]","using-server-sent-events-and-redis-to-keep-a-vue-application-updated",["Date","2023-06-18T00:00:00.000Z"],["Date","2026-11-13T00:00:00.000Z"],{"project_url":15,"github":15,"icons":257},[],[],{"title":260,"excerpt":261,"slug":264,"date":265,"modified":266,"readingTime":30,"acf":267,"tags":268,"author":18,"featuredMediaUrl":269,"featuredMedia":270},"Adding syntax highlighting to my blog with Torchlight",{"raw":262,"plain":263},"\u003Cp>Background A little while ago I read the following blog post, Web Component for a Code Block by Chris Coyier, it got me thinking about how I could enhance the code blocks on my website. This is a tech blog after all, and from time to time I want to share tips and tricks which involve [&hellip;]\u003C\u002Fp>","Background A little while ago I read the following blog post, Web Component for a Code Block by Chris Coyier, it got me thinking about how I could enhance the code blocks on my website. This is a tech blog after all, and from time to time I want to share tips and tricks which involve […]","adding-syntax-highlighting-to-my-blog-with-torchlight",["Date","2023-06-16T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],"https:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002Fe1e861af4f6259527d0fc8ad9790f0070bbbbc9e3026b722390c4894a9472d28\u002Fe1e861af4f625952-NVIDIA-GeForce-Overlay-DT-2023-06-16-14.07.00-1.png","https:\u002F\u002Fjoebailey.xyz\u002Fcdn-cgi\u002Fimage\u002Fformat=auto,quality=80,fit=scale-down,width=1200\u002Fhttps:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002Fe1e861af4f6259527d0fc8ad9790f0070bbbbc9e3026b722390c4894a9472d28\u002Fe1e861af4f625952-NVIDIA-GeForce-Overlay-DT-2023-06-16-14.07.00-1.png",{"title":272,"excerpt":273,"slug":276,"date":277,"modified":278,"readingTime":147,"acf":279,"tags":280,"author":18,"featuredMediaUrl":281,"featuredMedia":282},"Adding Algolia search to my 404 page",{"raw":274,"plain":275},"\u003Cp>My site is pretty small, so I don&#8217;t really need search functionality, but the Netlify Algolia plugin looked easy enough to use and a bit of fun. Diving into the docs, I got a bit overwhelmed. Algolia&#8217;s breadth of capabilities is huge. They definitely seem like the go-to for fully customisable search on a Jamstack [&hellip;]\u003C\u002Fp>","My site is pretty small, so I don’t really need search functionality, but the Netlify Algolia plugin looked easy enough to use and a bit of fun. Diving into the docs, I got a bit overwhelmed. Algolia’s breadth of capabilities is huge. They definitely seem like the go-to for fully customisable search on a Jamstack […]","adding-algolia-search-to-my-404-page",["Date","2023-06-13T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],"https:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002F109426a35d77479e1e989a82f03158349304fd98349223e9939110f26ef80377\u002F109426a35d77479e-NVIDIA-GeForce-Overlay-DT-2023-06-13-21.09.10.png","https:\u002F\u002Fjoebailey.xyz\u002Fcdn-cgi\u002Fimage\u002Fformat=auto,quality=80,fit=scale-down,width=1200\u002Fhttps:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002F109426a35d77479e1e989a82f03158349304fd98349223e9939110f26ef80377\u002F109426a35d77479e-NVIDIA-GeForce-Overlay-DT-2023-06-13-21.09.10.png",{"title":284,"excerpt":285,"slug":288,"date":289,"modified":290,"readingTime":147,"acf":291,"tags":295,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Notification of a delivery failure in Mailgun",{"raw":286,"plain":287},"\u003Cp>When you use an email service provider like Gmail, you get delivery failure emails. These emails are great because it enables you to reach out to your intended recipient in another way, or just retry the email later on. Because I use Mailgun with Gmail&#8217;s Send Mail As feature for my custom domains, I needed [&hellip;]\u003C\u002Fp>","When you use an email service provider like Gmail, you get delivery failure emails. These emails are great because it enables you to reach out to your intended recipient in another way, or just retry the email later on. Because I use Mailgun with Gmail’s Send Mail As feature for my custom domains, I needed […]","notification-of-a-delivery-failure-in-mailgun",["Date","2023-05-31T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],{"project_url":15,"github":292,"icons":293},"https:\u002F\u002Fgithub.com\u002FGalexia-Agency\u002Fmailgun-failed-webhook",[294],"Custom",[],{"title":297,"excerpt":298,"slug":301,"date":302,"modified":303,"readingTime":54,"acf":304,"tags":306,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Setting up Redis object caching in WordPress",{"raw":299,"plain":300},"\u003Cp>WordPress makes a lot of database queries. Even a fairly simple page can involve dozens of queries to fetch settings, posts, metadata and other information. WordPress has a built-in object cache which avoids repeating queries within the same request. The problem is that, by default, the cache disappears as soon as the request finishes. The [&hellip;]\u003C\u002Fp>","WordPress makes a lot of database queries. Even a fairly simple page can involve dozens of queries to fetch settings, posts, metadata and other information. WordPress has a built-in object cache which avoids repeating queries within the same request. The problem is that, by default, the cache disappears as soon as the request finishes. The […]","setting-up-redis-object-caching-in-wordpress",["Date","2023-05-17T00:00:00.000Z"],["Date","2026-11-10T00:00:00.000Z"],{"project_url":15,"github":15,"icons":305},[],[],{"title":308,"excerpt":309,"slug":312,"date":313,"modified":314,"readingTime":147,"acf":315,"tags":317,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Automatically adding favicons to external links",{"raw":310,"plain":311},"\u003Cp>I wanted to make external links on my blog a bit more visually interesting. Rather than displaying just the link text, I thought it would be nice to show the website&#8217;s favicon alongside it. It&#8217;s a small detail, but it makes links more recognisable, particularly when referencing GitHub, documentation or other websites throughout a post. [&hellip;]\u003C\u002Fp>","I wanted to make external links on my blog a bit more visually interesting. Rather than displaying just the link text, I thought it would be nice to show the website’s favicon alongside it. It’s a small detail, but it makes links more recognisable, particularly when referencing GitHub, documentation or other websites throughout a post. […]","automatically-adding-favicons-to-external-links",["Date","2023-04-08T00:00:00.000Z"],["Date","2026-11-11T00:00:00.000Z"],{"project_url":15,"github":15,"icons":316},[],[],{"title":319,"excerpt":320,"slug":323,"date":324,"modified":325,"readingTime":158,"acf":326,"tags":327,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Dragging to Scroll with JavaScript",{"raw":321,"plain":322},"\u003Cp>My portfolio has some cards to showcase projects and blog posts. On mobile, these cards display in a horizontal slider, which is easy enough to scroll on a touchscreen, or trackpad, but what if someone is viewing the website at a small size on a device with a mouse? Well they can of course use [&hellip;]\u003C\u002Fp>","My portfolio has some cards to showcase projects and blog posts. On mobile, these cards display in a horizontal slider, which is easy enough to scroll on a touchscreen, or trackpad, but what if someone is viewing the website at a small size on a device with a mouse? Well they can of course use […]","dragging-to-scroll-with-javascript",["Date","2023-03-30T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":4,"excerpt":329,"slug":8,"date":330,"modified":331,"readingTime":13,"acf":332,"tags":334,"author":18,"featuredMediaUrl":19,"featuredMedia":19},{"raw":6,"plain":7},["Date","2023-03-19T00:00:00.000Z"],["Date","2026-11-13T00:00:00.000Z"],{"project_url":15,"github":15,"icons":333},[],[],{"title":336,"excerpt":337,"slug":340,"date":341,"modified":342,"readingTime":147,"acf":343,"tags":345,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Fixing 100vw and scrollbar width across different devices",{"raw":338,"plain":339},"\u003Cp>I came across a frustrating CSS issue while working on my personal website. I had a section which needed to stretch across the full width of the browser window. Normally, you&#8217;d expect width: 100vw to do exactly that. It worked perfectly on some devices, but on others, the element was slightly wider than the page [&hellip;]\u003C\u002Fp>","I came across a frustrating CSS issue while working on my personal website. I had a section which needed to stretch across the full width of the browser window. Normally, you’d expect width: 100vw to do exactly that. It worked perfectly on some devices, but on others, the element was slightly wider than the page […]","fixing-100vw-and-scrollbar-width-across-different-devices",["Date","2023-03-14T00:00:00.000Z"],["Date","2026-11-11T00:00:00.000Z"],{"project_url":15,"github":15,"icons":344},[],[],{"title":347,"excerpt":348,"slug":351,"date":352,"modified":353,"readingTime":13,"acf":354,"tags":356,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Using Google Sheets as a database for a Vue application",{"raw":349,"plain":350},"\u003Cp>I had a client who needed to display a list of churches taking part in an event. The list needed to be organised by area, include information about each church, and allow visitors to download a PDF they could print off. The information was already being managed in Google Sheets, so rather than move everything [&hellip;]\u003C\u002Fp>","I had a client who needed to display a list of churches taking part in an event. The list needed to be organised by area, include information about each church, and allow visitors to download a PDF they could print off. The information was already being managed in Google Sheets, so rather than move everything […]","using-google-sheets-as-a-database-for-a-vue-application",["Date","2022-11-08T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],{"project_url":15,"github":15,"icons":355},[],[],[358,369,380,391,402,413,423,437,450,463],{"title":359,"excerpt":360,"slug":363,"date":364,"modified":365,"readingTime":147,"acf":366,"tags":368,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Replacing WP-Cron with a proper scheduler",{"raw":361,"plain":362},"\u003Cp>WordPress has a built-in task scheduling system called WP-Cron. It handles things like publishing scheduled posts, checking for updates and running background tasks created by plugins. The strange thing about WP-Cron is that, by default, it doesn&#8217;t run on a proper schedule. Instead, WordPress checks whether any tasks are due when somebody visits the website. [&hellip;]\u003C\u002Fp>","WordPress has a built-in task scheduling system called WP-Cron. It handles things like publishing scheduled posts, checking for updates and running background tasks created by plugins. The strange thing about WP-Cron is that, by default, it doesn’t run on a proper schedule. Instead, WordPress checks whether any tasks are due when somebody visits the website. […]","replacing-wp-cron-with-a-proper-scheduler",["Date","2022-10-21T00:00:00.000Z"],["Date","2026-11-10T00:00:00.000Z"],{"project_url":15,"github":15,"icons":367},[],[],{"title":370,"excerpt":371,"slug":374,"date":375,"modified":376,"readingTime":147,"acf":377,"tags":379,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Backing up WordPress to Cloudflare R2",{"raw":372,"plain":373},"\u003Cp>I&#8217;ve been using Cloudflare R2 as somewhere to store WordPress backups. Keeping backups on the same server as the website isn&#8217;t particularly useful if that server suffers a serious failure. You might be able to recover from a bad plugin update, but you won&#8217;t have much luck if the entire server disappears. I wanted the [&hellip;]\u003C\u002Fp>","I’ve been using Cloudflare R2 as somewhere to store WordPress backups. Keeping backups on the same server as the website isn’t particularly useful if that server suffers a serious failure. You might be able to recover from a bad plugin update, but you won’t have much luck if the entire server disappears. I wanted the […]","backing-up-wordpress-to-cloudflare-r2",["Date","2022-05-22T00:00:00.000Z"],["Date","2026-11-10T00:00:00.000Z"],{"project_url":15,"github":15,"icons":378},[],[],{"title":381,"excerpt":382,"slug":385,"date":386,"modified":387,"readingTime":100,"acf":388,"tags":390,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Automatically generating website screenshots with Puppeteer and Nuxt",{"raw":383,"plain":384},"\u003Cp>I was working on a Nuxt website which had a portfolio of websites I&#8217;d built. Each project had a title, description, link to the website and an image showing what it looked like. The problem was keeping those images up to date. Every time a website changed, I&#8217;d have to take another screenshot, crop it [&hellip;]\u003C\u002Fp>","I was working on a Nuxt website which had a portfolio of websites I’d built. Each project had a title, description, link to the website and an image showing what it looked like. The problem was keeping those images up to date. Every time a website changed, I’d have to take another screenshot, crop it […]","automatically-generating-website-screenshots-with-puppeteer-and-nuxt",["Date","2021-09-22T00:00:00.000Z"],["Date","2026-11-11T00:00:00.000Z"],{"project_url":15,"github":15,"icons":389},[],[],{"title":392,"excerpt":393,"slug":396,"date":397,"modified":398,"readingTime":13,"acf":399,"tags":401,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Migrating WordPress between servers without unnecessary downtime",{"raw":394,"plain":395},"\u003Cp>Moving a WordPress website between servers is usually fairly straightforward. The difficult part is making sure everything works on the new server before directing visitors to it. I&#8217;ve migrated WordPress sites between hosting providers, and the approach I prefer is to get the entire website working on the new server before making any DNS changes. [&hellip;]\u003C\u002Fp>","Moving a WordPress website between servers is usually fairly straightforward. The difficult part is making sure everything works on the new server before directing visitors to it. I’ve migrated WordPress sites between hosting providers, and the approach I prefer is to get the entire website working on the new server before making any DNS changes. […]","migrating-wordpress-between-servers-without-unnecessary-downtime",["Date","2021-08-13T00:00:00.000Z"],["Date","2026-11-10T00:00:00.000Z"],{"project_url":15,"github":15,"icons":400},[],[],{"title":403,"excerpt":404,"slug":407,"date":408,"modified":409,"readingTime":147,"acf":410,"tags":412,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Scraping a website to migrate it to WordPress",{"raw":405,"plain":406},"\u003Cp>I was tasked with migrating an old website from Yahoo Pages to WordPress. Normally, migrating a website is fairly straightforward if you can export its content or access the database. Unfortunately, that wasn&#8217;t an option here. The website contained a collection of articles about local and world history. I needed to get that content out [&hellip;]\u003C\u002Fp>","I was tasked with migrating an old website from Yahoo Pages to WordPress. Normally, migrating a website is fairly straightforward if you can export its content or access the database. Unfortunately, that wasn’t an option here. The website contained a collection of articles about local and world history. I needed to get that content out […]","scraping-a-website-to-migrate-it-to-wordpress",["Date","2021-03-08T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],{"project_url":15,"github":15,"icons":411},[],[],{"title":414,"excerpt":415,"slug":418,"date":419,"modified":420,"readingTime":42,"acf":421,"tags":422,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"How To Secure your server using UFW and Cloudflare",{"raw":416,"plain":417},"\u003Cp>This guide will help you use UFW (uncomplicated firewall) to block all traffic to your server other than traffic coming via Cloudflare, or for essential operations such as FTP and SSH. Why would you want to do this? Cloudflare will protect your site from all manner of attacks. However, your server may still be vulnerable. [&hellip;]\u003C\u002Fp>","This guide will help you use UFW (uncomplicated firewall) to block all traffic to your server other than traffic coming via Cloudflare, or for essential operations such as FTP and SSH. Why would you want to do this? Cloudflare will protect your site from all manner of attacks. However, your server may still be vulnerable. […]","how-to-secure-your-server-using-ufw-and-cloudflare",["Date","2021-01-11T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":424,"excerpt":425,"slug":428,"date":429,"modified":430,"readingTime":158,"acf":431,"tags":436,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Adobe XD Challenge Calculator",{"raw":426,"plain":427},"\u003Cp>The third challenge was to create a calculator. I wanted to practise my CSS art and so chose an image of an old Casio calculator and tried to recreate it as closely as possible. It was quite a challenge creating the code for the calculator to function. It wasn&#8217;t as simple as just adding numbers [&hellip;]\u003C\u002Fp>","The third challenge was to create a calculator. I wanted to practise my CSS art and so chose an image of an old Casio calculator and tried to recreate it as closely as possible. It was quite a challenge creating the code for the calculator to function. It wasn’t as simple as just adding numbers […]","adobe-xd-challenge-calculator",["Date","2020-08-06T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":432,"github":433,"icons":434},"https:\u002F\u002Fvue-calculator-joe.netlify.app","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FVue-Calculator",[435],"Vue",[],{"title":438,"excerpt":439,"slug":442,"date":443,"modified":444,"readingTime":158,"acf":445,"tags":449,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Adobe XD Challenge Fitness App",{"raw":440,"plain":441},"\u003Cp>The second challenge was to create a fitness app. I chose to make an activity tracker. I took inspiration from Lifelog, an app by Sony that I used for many years. I used the Google Maps API and created a component in Vue for the activities.\u003C\u002Fp>","The second challenge was to create a fitness app. I chose to make an activity tracker. I took inspiration from Lifelog, an app by Sony that I used for many years. I used the Google Maps API and created a component in Vue for the activities.","adobe-xd-challenge-fitness-app",["Date","2020-08-06T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":446,"github":447,"icons":448},"https:\u002F\u002Fjoe-exercise.netlify.app","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FExercise-App",[435],[],{"title":451,"excerpt":452,"slug":455,"date":456,"modified":457,"readingTime":158,"acf":458,"tags":462,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Adobe XD Challenge – Music Player",{"raw":453,"plain":454},"\u003Cp>Me and Eleanor Morgan participated in the Adobe XD Challenge. I wanted to use Vue.js rather than practise my skills in XD as I view myself as more of a developer than a designer. The first challenge was to create a music player. I played around with the audio API and used some CSS animations.\u003C\u002Fp>","Me and Eleanor Morgan participated in the Adobe XD Challenge. I wanted to use Vue.js rather than practise my skills in XD as I view myself as more of a developer than a designer. The first challenge was to create a music player. I played around with the audio API and used some CSS animations.","adobe-xd-challenge-music-player",["Date","2020-08-06T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":459,"github":460,"icons":461},"http:\u002F\u002Fmusic-player.joebailey.xyz\u002F","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002Fmusic-player",[435],[],{"title":464,"excerpt":465,"slug":468,"date":469,"modified":470,"readingTime":54,"acf":471,"tags":472,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"React and Vue re-visited",{"raw":466,"plain":467},"\u003Cp>After re-creating my portfolio with Gatsby (which uses React) and building an app for a client in Vue I feel I have more knowledge about the two JavaScript frameworks to give an informed opinion on both. When I was first starting out using these sort of frameworks I wrote this article on my thoughts. I [&hellip;]\u003C\u002Fp>","After re-creating my portfolio with Gatsby (which uses React) and building an app for a client in Vue I feel I have more knowledge about the two JavaScript frameworks to give an informed opinion on both. When I was first starting out using these sort of frameworks I wrote this article on my thoughts. I […]","react-and-vue-re-visited",["Date","2020-05-09T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],[474,484,498,510,523,533,547,561,574,587],{"title":475,"excerpt":476,"slug":479,"date":480,"modified":481,"readingTime":42,"acf":482,"tags":483,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"What is a Front-End Developer?",{"raw":477,"plain":478},"\u003Cp>I&#8217;ve been asking myself this question a lot recently after applying for quite a few web development placements. The experience has been enlightening, teaching me a great deal about the industry. I&#8217;ve been reflecting on what I&#8217;m good at in web development and what I enjoy. Whilst freelancing I&#8217;d describe myself as a Full-Stack Developer. [&hellip;]\u003C\u002Fp>","I’ve been asking myself this question a lot recently after applying for quite a few web development placements. The experience has been enlightening, teaching me a great deal about the industry. I’ve been reflecting on what I’m good at in web development and what I enjoy. Whilst freelancing I’d describe myself as a Full-Stack Developer. […]","what-is-a-front-end-developer",["Date","2020-05-07T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":485,"excerpt":486,"slug":489,"date":490,"modified":491,"readingTime":158,"acf":492,"tags":497,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Points Of Interest",{"raw":487,"plain":488},"\u003Cp>Following on from developing the HitTastic! site we had to create a site in PHP that allows users to view POI&#8217;s stored in a database, and contribute to this database. We had to do some basic styles to go along with it.\u003C\u002Fp>","Following on from developing the HitTastic! site we had to create a site in PHP that allows users to view POI’s stored in a database, and contribute to this database. We had to do some basic styles to go along with it.","points-of-interest",["Date","2020-04-28T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":493,"github":494,"icons":495},"https:\u002F\u002Fpoi.joebailey.xyz\u002F","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FPointsOfInterest-MySQLi",[496],"PHP",[],{"title":499,"excerpt":500,"slug":503,"date":504,"modified":505,"readingTime":158,"acf":506,"tags":509,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"HitTastic!",{"raw":501,"plain":502},"\u003Cp>In my second year of university, I did a PHP unit where we were tasked with creating a site showing the number one songs from a certain year. We had to make a login, connect to a database, and perform a search.\u003C\u002Fp>","In my second year of university, I did a PHP unit where we were tasked with creating a site showing the number one songs from a certain year. We had to make a login, connect to a database, and perform a search.","hittastic",["Date","2020-04-28T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":507,"icons":508},"https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FPHP-Lessons",[496],[],{"title":511,"excerpt":512,"slug":515,"date":516,"modified":517,"readingTime":158,"acf":518,"tags":522,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Hangman",{"raw":513,"plain":514},"\u003Cp>Whilst I was searching for a placement I had a few tasks to complete for various roles I was interviewing for. One of these tasks was to create a simple Hangman game in PHP.\u003C\u002Fp>","Whilst I was searching for a placement I had a few tasks to complete for various roles I was interviewing for. One of these tasks was to create a simple Hangman game in PHP.","hangman",["Date","2020-04-28T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":519,"github":520,"icons":521},"https:\u002F\u002Fhangman.joebailey.xyz\u002F","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FHangman",[496],[],{"title":524,"excerpt":525,"slug":528,"date":529,"modified":530,"readingTime":42,"acf":531,"tags":532,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Re-creating my portfolio with Gatsby",{"raw":526,"plain":527},"\u003Cp>You may have read Re-creating my portfolio with React where I talked about my experiences converting my simple static portfolio into a React website, turning reusable objects on the page into React components. This inspired me to learn Gatsby, a static site generator that uses React. I was previously using my a category on my [&hellip;]\u003C\u002Fp>","You may have read Re-creating my portfolio with React where I talked about my experiences converting my simple static portfolio into a React website, turning reusable objects on the page into React components. This inspired me to learn Gatsby, a static site generator that uses React. I was previously using my a category on my […]","re-creating-my-portfolio-with-gatsby",["Date","2020-04-27T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":534,"excerpt":535,"slug":538,"date":539,"modified":540,"readingTime":158,"acf":541,"tags":546,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Firebase Chat App",{"raw":536,"plain":537},"\u003Cp>In my first year of University I had a go at creating a chat app using Firebase following my lecturers instructions. I was the only person in my class to succeed. Go ahead, type something.\u003C\u002Fp>","In my first year of University I had a go at creating a chat app using Firebase following my lecturers instructions. I was the only person in my class to succeed. Go ahead, type something.","firebase-chat-app",["Date","2020-04-24T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":542,"github":543,"icons":544},"https:\u002F\u002Fchat-app-a7f51.firebaseapp.com\u002F","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002Ffirebase_chat_app",[545],"Firebase",[],{"title":548,"excerpt":549,"slug":552,"date":553,"modified":554,"readingTime":158,"acf":555,"tags":560,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"React Tic Tac Toe",{"raw":550,"plain":551},"\u003Cp>My first venture into the world of React was building this Tic Tac Toe game using the React Docs. It also helped me get to grips with CodePen for creating small little projects.\u003C\u002Fp>","My first venture into the world of React was building this Tic Tac Toe game using the React Docs. It also helped me get to grips with CodePen for creating small little projects.","react-tic-tac-toe",["Date","2020-04-24T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":556,"github":557,"icons":558},"https:\u002F\u002Fcodepen.io\u002Fjoebailey26\u002Fpen\u002FpoogwjN","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FReact-Tic-Tac-Toe",[559],"React",[],{"title":562,"excerpt":563,"slug":566,"date":567,"modified":568,"readingTime":158,"acf":569,"tags":573,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Vue To-Do List",{"raw":564,"plain":565},"\u003Cp>After experimenting using Vue in my Photography Website I decided to have a go at making a To-Do List. You can create and delete To Dos but they don&#8217;t currently persist. This is something I wish to add in the future.\u003C\u002Fp>","After experimenting using Vue in my Photography Website I decided to have a go at making a To-Do List. You can create and delete To Dos but they don’t currently persist. This is something I wish to add in the future.","vue-to-do-list",["Date","2020-04-24T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":570,"github":571,"icons":572},"https:\u002F\u002Frelaxed-cori-9310e4.netlify.app\u002F","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FVue-To-Do-List",[435],[],{"title":575,"excerpt":576,"slug":579,"date":580,"modified":581,"readingTime":158,"acf":582,"tags":586,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"To-Do List",{"raw":577,"plain":578},"\u003Cp>As part of my university course we were tasked with creating a To Do List in Vanilla JavaScript. For many, it was also an introduction into using NPM and BrowserSync for the first time. We also experimented with using Local Browser Storage.\u003C\u002Fp>","As part of my university course we were tasked with creating a To Do List in Vanilla JavaScript. For many, it was also an introduction into using NPM and BrowserSync for the first time. We also experimented with using Local Browser Storage.","to-do-list",["Date","2020-04-24T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":583,"github":584,"icons":585},"https:\u002F\u002Fcodepen.io\u002Fjoebailey26\u002Fpen\u002FwvvbBpN","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FJavaScript-To-Do-List",[294],[],{"title":588,"excerpt":589,"slug":592,"date":593,"modified":594,"readingTime":158,"acf":595,"tags":596,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Re-creating my Portfolio with React",{"raw":590,"plain":591},"\u003Cp>I&#8217;ve just finished recreating my portfolio using React. There aren&#8217;t very many visual changes apart from changing the hover effects a little. The site features a lot of repetitive content, such as portfolio items. React is good for this because I can create a component for the item which minimizes the amount of coding required. [&hellip;]\u003C\u002Fp>","I’ve just finished recreating my portfolio using React. There aren’t very many visual changes apart from changing the hover effects a little. The site features a lot of repetitive content, such as portfolio items. React is good for this because I can create a component for the item which minimizes the amount of coding required. […]","re-creating-my-portfolio-with-react",["Date","2020-02-03T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],[598,611,621,631,641,651,661,673,685,695],{"title":599,"excerpt":600,"slug":603,"date":604,"modified":605,"readingTime":158,"acf":606,"tags":610,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Weather app",{"raw":601,"plain":602},"\u003Cp>My friend made a weather app so that they could learn APIs. I decided to fork it and take it a step further by experimenting with CSS Houdini, Variable Fonts, CSS Variables, and new browser APIs such as the Speech Synthesis API. Features This App uses experimental&nbsp;CSS Houdini&nbsp;features that are currently only supported in Google [&hellip;]\u003C\u002Fp>","My friend made a weather app so that they could learn APIs. I decided to fork it and take it a step further by experimenting with CSS Houdini, Variable Fonts, CSS Variables, and new browser APIs such as the Speech Synthesis API. Features This App uses experimental CSS Houdini features that are currently only supported in Google […]","weather-app",["Date","2020-02-01T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],{"project_url":607,"github":608,"icons":609},"https:\u002F\u002Fjoebailey26.github.io\u002FWeather-App\u002Findex.html","https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002FWeather-App",[294],[],{"title":612,"excerpt":613,"slug":616,"date":617,"modified":618,"readingTime":42,"acf":619,"tags":620,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"A new Portfolio with Vue.js – Joe Bailey Photography",{"raw":614,"plain":615},"\u003Cp>To mark the beginning of my second year at University I thought that I would try learning a new skill, namely Vue.js. I usually struggle to learn something if I don&#8217;t see any real practical application for it, but this time I figured I could turn my photography portfolio into a dynamic Vue.js enhanced page. [&hellip;]\u003C\u002Fp>","To mark the beginning of my second year at University I thought that I would try learning a new skill, namely Vue.js. I usually struggle to learn something if I don’t see any real practical application for it, but this time I figured I could turn my photography portfolio into a dynamic Vue.js enhanced page. […]","a-new-portfolio-with-vue-js",["Date","2019-12-07T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":622,"excerpt":623,"slug":626,"date":627,"modified":628,"readingTime":158,"acf":629,"tags":630,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Cookie Consent – Joe Bailey Photography",{"raw":624,"plain":625},"\u003Cp>Over the past couple of months, I&#8217;ve added some Analytics software to my site such as Google Analytics and Facebook Pixel. As my website serves customers in the EU, the Cookie Law applies. I have used JavaScript to ensure that cookies are not fired until the user has explicitly accepted them on the website. A [&hellip;]\u003C\u002Fp>","Over the past couple of months, I’ve added some Analytics software to my site such as Google Analytics and Facebook Pixel. As my website serves customers in the EU, the Cookie Law applies. I have used JavaScript to ensure that cookies are not fired until the user has explicitly accepted them on the website. A […]","cookie-consent",["Date","2019-12-07T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":632,"excerpt":633,"slug":636,"date":637,"modified":638,"readingTime":42,"acf":639,"tags":640,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Re-doing my portfolio",{"raw":634,"plain":635},"\u003Cp>I built my web design portfolio site over the Christmas of 2018 and decided it needed a bit of a refresh during the summer. I got in touch with the @_tashhockey, the person behind Honest Feedback, to give my website a bit of a review. She made some really good points and brought to light [&hellip;]\u003C\u002Fp>","I built my web design portfolio site over the Christmas of 2018 and decided it needed a bit of a refresh during the summer. I got in touch with the @_tashhockey, the person behind Honest Feedback, to give my website a bit of a review. She made some really good points and brought to light […]","re-doing-my-portfolio",["Date","2019-10-07T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":642,"excerpt":643,"slug":646,"date":647,"modified":648,"readingTime":158,"acf":649,"tags":650,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Animated mobile menu",{"raw":644,"plain":645},"\u003Cp>In quite a few of my projects I&#8217;ve needed a quick and easy way of making a menu which scales onto mobile. I like the style that android apps have; the animated drawer, so I have replicated this. The menu is swipe aware using Hammer.js which means if you swipe right on mobile then the [&hellip;]\u003C\u002Fp>","In quite a few of my projects I’ve needed a quick and easy way of making a menu which scales onto mobile. I like the style that android apps have; the animated drawer, so I have replicated this. The menu is swipe aware using Hammer.js which means if you swipe right on mobile then the […]","animated-mobile-menu",["Date","2019-09-30T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":652,"excerpt":653,"slug":656,"date":657,"modified":658,"readingTime":158,"acf":659,"tags":660,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"No scroll whilst lightbox is open – Joe Bailey Photography",{"raw":654,"plain":655},"\u003Cp>I read the following article by CSS Tricks https:\u002F\u002Fcss-tricks.com\u002Fprevent-page-scrolling-when-a-modal-is-open\u002F and decided that my website shouldn&#8217;t scroll whilst the lightbox is shown, i.e. on the Blog and the Memories pages when an image is clicked. I use the Featherlight plugin on my Blog and the equivalent for the Memories page. I added the following CSS to [&hellip;]\u003C\u002Fp>","I read the following article by CSS Tricks https:\u002F\u002Fcss-tricks.com\u002Fprevent-page-scrolling-when-a-modal-is-open\u002F and decided that my website shouldn’t scroll whilst the lightbox is shown, i.e. on the Blog and the Memories pages when an image is clicked. I use the Featherlight plugin on my Blog and the equivalent for the Memories page. I added the following CSS to […]","no-scroll-whilst-lightbox-is-open",["Date","2019-08-01T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":662,"excerpt":663,"slug":666,"date":667,"modified":668,"readingTime":158,"acf":669,"tags":670,"author":18,"featuredMediaUrl":671,"featuredMedia":672},"Dark Mode – Joe Bailey Photography",{"raw":664,"plain":665},"\u003Cp>I read the following article: https:\u002F\u002Fwww.howtogeek.com\u002F434592\u002Fwhat%E2%80%99s-new-in-chrome-76-arriving-july-30th\u002F and was excited by &#8220;Automatic Dark Mode is Coming to Websites&#8221;. I decided to give it a shot. I read up on the Media Queries Level 5 spec and added the &#8220;prefers-color-scheme&#8221; media query to my CSS. I downloaded a copy of Chrome Canary to test the website, as [&hellip;]\u003C\u002Fp>","I read the following article: https:\u002F\u002Fwww.howtogeek.com\u002F434592\u002Fwhat%E2%80%99s-new-in-chrome-76-arriving-july-30th\u002F and was excited by “Automatic Dark Mode is Coming to Websites”. I decided to give it a shot. I read up on the Media Queries Level 5 spec and added the “prefers-color-scheme” media query to my CSS. I downloaded a copy of Chrome Canary to test the website, as […]","dark-mode",["Date","2019-07-29T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],"https:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002F18ef7e8b3ffaa78e6531a8e7db4678f61b89f836e42519209287c63e325266b7\u002F18ef7e8b3ffaa78e-dark-mode.jpg","https:\u002F\u002Fjoebailey.xyz\u002Fcdn-cgi\u002Fimage\u002Fformat=auto,quality=80,fit=scale-down,width=1200\u002Fhttps:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002F18ef7e8b3ffaa78e6531a8e7db4678f61b89f836e42519209287c63e325266b7\u002F18ef7e8b3ffaa78e-dark-mode.jpg",{"title":674,"excerpt":675,"slug":678,"date":679,"modified":680,"readingTime":158,"acf":681,"tags":682,"author":18,"featuredMediaUrl":683,"featuredMedia":684},"Joe Bailey Photography is now a Progressive Web App",{"raw":676,"plain":677},"\u003Cp>Making my site work offline had been on my bucket list for a while. It was the last thing in Google Chrome&#8217;s Lighthouse test to complete. The site now has close to 100 scores across all areas in Google Chrome&#8217;s Lighthouse Audit. I read up on Service Workers on both Google Developers and MDN Web [&hellip;]\u003C\u002Fp>","Making my site work offline had been on my bucket list for a while. It was the last thing in Google Chrome’s Lighthouse test to complete. The site now has close to 100 scores across all areas in Google Chrome’s Lighthouse Audit. I read up on Service Workers on both Google Developers and MDN Web […]","joe-bailey-photography-is-now-a-progressive-web-app",["Date","2019-07-18T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],"https:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002F5464e472fd92822b761ed6e957448e480b6c12f6291d9ae23f168b154c37439d\u002F5464e472fd92822b-Untitled-1.jpg","https:\u002F\u002Fjoebailey.xyz\u002Fcdn-cgi\u002Fimage\u002Fformat=auto,quality=80,fit=scale-down,width=1200\u002Fhttps:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002F5464e472fd92822b761ed6e957448e480b6c12f6291d9ae23f168b154c37439d\u002F5464e472fd92822b-Untitled-1.jpg",{"title":686,"excerpt":687,"slug":690,"date":691,"modified":692,"readingTime":158,"acf":693,"tags":694,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Data Flow Diagrams",{"raw":688,"plain":689},"\u003Cp>When we first started doing Data Flow Diagrams at the start of the semester I thought they were the worst thing in the world. However, as I came to understand them more and start to think about more real-world business problems that I was encountering such as creating a website for my Dad or as [&hellip;]\u003C\u002Fp>","When we first started doing Data Flow Diagrams at the start of the semester I thought they were the worst thing in the world. However, as I came to understand them more and start to think about more real-world business problems that I was encountering such as creating a website for my Dad or as […]","data-flow-diagrams",["Date","2019-04-19T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":545,"excerpt":696,"slug":699,"date":700,"modified":701,"readingTime":158,"acf":702,"tags":703,"author":18,"featuredMediaUrl":19,"featuredMedia":19},{"raw":697,"plain":698},"\u003Cp>During our Web Technologies practical lessons, we have to follow a set of instructions to create a website or simple script. The lecturer has hosted the notes for the unit on Firebase. I&#8217;d looked into Firebase before but couldn&#8217;t understand how to use it. However, when an extended task came up in lesson I gave [&hellip;]\u003C\u002Fp>","During our Web Technologies practical lessons, we have to follow a set of instructions to create a website or simple script. The lecturer has hosted the notes for the unit on Firebase. I’d looked into Firebase before but couldn’t understand how to use it. However, when an extended task came up in lesson I gave […]","firebase",["Date","2019-04-19T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],[705,715,725,735,747,759,769,779,789,799],{"title":706,"excerpt":707,"slug":710,"date":711,"modified":712,"readingTime":158,"acf":713,"tags":714,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Angular, React, Vue, Node",{"raw":708,"plain":709},"\u003Cp>Whilst working on various projects this semester I&#8217;ve encountered the following technologies: Angular, React, Vue, and Node. I&#8217;ve experimented with them to some extent and found Node Package Manager quite useful. Initially, when I first heard about them I thought they were JavaScript Libraries like JQuery, however, they are far from it. Each technology is [&hellip;]\u003C\u002Fp>","Whilst working on various projects this semester I’ve encountered the following technologies: Angular, React, Vue, and Node. I’ve experimented with them to some extent and found Node Package Manager quite useful. Initially, when I first heard about them I thought they were JavaScript Libraries like JQuery, however, they are far from it. Each technology is […]","angular-react-vue-node",["Date","2019-04-19T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":716,"excerpt":717,"slug":720,"date":721,"modified":722,"readingTime":54,"acf":723,"tags":724,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"New Laptop, Deployment Manager, NAS and VPN",{"raw":718,"plain":719},"\u003Cp>I&#8217;d wanted to buy a new laptop for ages as mine was quite heavy and I wanted to take it to lectures, and store it in my bag but couldn&#8217;t. This meant I was looking for something quite light. I also wanted something powerful as I do a lot of media production and web development. [&hellip;]\u003C\u002Fp>","I’d wanted to buy a new laptop for ages as mine was quite heavy and I wanted to take it to lectures, and store it in my bag but couldn’t. This meant I was looking for something quite light. I also wanted something powerful as I do a lot of media production and web development. […]","new-laptop-deployment-manager-nas-and-vpn",["Date","2019-04-16T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":726,"excerpt":727,"slug":730,"date":731,"modified":732,"readingTime":158,"acf":733,"tags":734,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Git",{"raw":728,"plain":729},"\u003Cp>As part of my studies at Solent, we were introduced to using Git in our workflow. Git is useful as it means mistakes can be corrected by rolling back the code to a version that worked. I&#8217;d come across this before at Fluid Studios Ltd. where they were using Git Kraken, however, I&#8217;d never really [&hellip;]\u003C\u002Fp>","As part of my studies at Solent, we were introduced to using Git in our workflow. Git is useful as it means mistakes can be corrected by rolling back the code to a version that worked. I’d come across this before at Fluid Studios Ltd. where they were using Git Kraken, however, I’d never really […]","git",["Date","2019-04-16T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":736,"excerpt":737,"slug":740,"date":741,"modified":742,"readingTime":158,"acf":743,"tags":746,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"No Thumbnails Gallery",{"raw":738,"plain":739},"\u003Cp>When creating my portfolio and memories page I needed a quick and easy way of creating galleries of images without having to code each individual image (there are over 3,000 images!). Also, I needed it without a thumbnail for each image. As far as I can tell there isn&#8217;t an easy option to do that. [&hellip;]\u003C\u002Fp>","When creating my portfolio and memories page I needed a quick and easy way of creating galleries of images without having to code each individual image (there are over 3,000 images!). Also, I needed it without a thumbnail for each image. As far as I can tell there isn’t an easy option to do that. […]","no-thumbnails-gallery",["Date","2019-02-06T00:00:00.000Z"],["Date","2026-11-08T00:00:00.000Z"],{"project_url":15,"github":744,"icons":745},"https:\u002F\u002Fgithub.com\u002Fjoebailey26\u002Fno-thumbnails-gallery",[496],[],{"title":748,"excerpt":749,"slug":752,"date":753,"modified":754,"readingTime":158,"acf":755,"tags":756,"author":18,"featuredMediaUrl":757,"featuredMedia":758},"Web Design Portfolio",{"raw":750,"plain":751},"\u003Cp>I&#8217;d been meaning to update my portfolio site for quite a while as I wasn&#8217;t really happy with the design. The long Christmas break gave me a chance to do just that. I was inspired by this template. In my lectures, we&#8217;d been learning about Figma and UX design. I wanted to use this redesign [&hellip;]\u003C\u002Fp>","I’d been meaning to update my portfolio site for quite a while as I wasn’t really happy with the design. The long Christmas break gave me a chance to do just that. I was inspired by this template. In my lectures, we’d been learning about Figma and UX design. I wanted to use this redesign […]","web-design-portfolio",["Date","2019-02-06T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],"https:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002F34a32b062918590e903c5210aa0d081d9960608cd1c70ece44100590aeafc4a6\u002F34a32b062918590e-Logo.png","https:\u002F\u002Fjoebailey.xyz\u002Fcdn-cgi\u002Fimage\u002Fformat=auto,quality=80,fit=scale-down,width=1200\u002Fhttps:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002F34a32b062918590e903c5210aa0d081d9960608cd1c70ece44100590aeafc4a6\u002F34a32b062918590e-Logo.png",{"title":760,"excerpt":761,"slug":764,"date":765,"modified":766,"readingTime":158,"acf":767,"tags":768,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Transferring my hosting package from 1and1 IONOS to iPage",{"raw":762,"plain":763},"\u003Cp>I think transferring hosting provider was the single greatest nightmare I&#8217;ve had in my life. I bought a few domains with 1and1, hoping to get SSL certificates that were advertised, however you only get one SSL certificate per contract. I&#8217;d have to create multiple contracts for all my domains, and then would not be able [&hellip;]\u003C\u002Fp>","I think transferring hosting provider was the single greatest nightmare I’ve had in my life. I bought a few domains with 1and1, hoping to get SSL certificates that were advertised, however you only get one SSL certificate per contract. I’d have to create multiple contracts for all my domains, and then would not be able […]","transferring-my-hosting-package-from-1and1-ionos-to-ipage",["Date","2019-02-06T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":770,"excerpt":771,"slug":774,"date":775,"modified":776,"readingTime":158,"acf":777,"tags":778,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Website Updates: Portfolio and Memories – Joe Bailey Photography",{"raw":772,"plain":773},"\u003Cp>After creating my Web Design portfolio site and transferring my hosting package I was in the mood to do some web design. I&#8217;d been meaning to add a portfolio to my photography site for a long while. Whilst making the photography portfolio I created some cool code to create a gallery of images which you [&hellip;]\u003C\u002Fp>","After creating my Web Design portfolio site and transferring my hosting package I was in the mood to do some web design. I’d been meaning to add a portfolio to my photography site for a long while. Whilst making the photography portfolio I created some cool code to create a gallery of images which you […]","website-updates-portfolio-and-memories",["Date","2019-02-01T00:00:00.000Z"],["Date","2026-11-03T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":780,"excerpt":781,"slug":784,"date":785,"modified":786,"readingTime":158,"acf":787,"tags":788,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Don’t forget your future self – Dan Benham",{"raw":782,"plain":783},"\u003Cp>I attended a talk by Dan Benham, set up by the university, which I found really interesting. He works for Studio Republic in Winchester. He was tired of working for corporate clients and started an ethical business to combat this. His business helps charities and other ethical entities with their creative tasks. An interesting question [&hellip;]\u003C\u002Fp>","I attended a talk by Dan Benham, set up by the university, which I found really interesting. He works for Studio Republic in Winchester. He was tired of working for corporate clients and started an ethical business to combat this. His business helps charities and other ethical entities with their creative tasks. An interesting question […]","dont-forget-your-future-self-dan-benham",["Date","2019-01-31T00:00:00.000Z"],["Date","2026-11-03T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":790,"excerpt":791,"slug":794,"date":795,"modified":796,"readingTime":54,"acf":797,"tags":798,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Web Components using PHP – Joe Bailey Photography",{"raw":792,"plain":793},"\u003Cp>In my Web Design and Development course, we touched on using web components and live style guides. This is the practice of having a set of components and styles that can be reused across your website. The advantage is that when something gets updated it rolls out across the whole site at the same time. [&hellip;]\u003C\u002Fp>","In my Web Design and Development course, we touched on using web components and live style guides. This is the practice of having a set of components and styles that can be reused across your website. The advantage is that when something gets updated it rolls out across the whole site at the same time. […]","web-components-using-php",["Date","2018-11-22T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":800,"excerpt":801,"slug":804,"date":805,"modified":806,"readingTime":158,"acf":807,"tags":808,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Wix Vs WordPress",{"raw":802,"plain":803},"\u003Cp>Now I&#8217;ve come across two instances of Wix in the past couple of months. Neither website was optimised for mobile. Wix has the option there, but it&#8217;s incredibly hard to use, you can change one thing and it will delete half the content on your site. The drag and drop editor is extremely slow, it&#8217;s [&hellip;]\u003C\u002Fp>","Now I’ve come across two instances of Wix in the past couple of months. Neither website was optimised for mobile. Wix has the option there, but it’s incredibly hard to use, you can change one thing and it will delete half the content on your site. The drag and drop editor is extremely slow, it’s […]","wix-vs-wordpress",["Date","2018-11-22T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],[810,820,832,842,852,862,872,882,896,906],{"title":811,"excerpt":812,"slug":815,"date":816,"modified":817,"readingTime":42,"acf":818,"tags":819,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Flickr changes to free accounts – Joe Bailey Photography",{"raw":813,"plain":814},"\u003Cp>You might notice that my website uses Flickr to display most of the images. This is done by using an iframe of the Flickr slideshow. The reason I chose to use Flickr to display my images is that I didn&#8217;t have much experience coding at the time (I&#8217;m still not a code genius). It was [&hellip;]\u003C\u002Fp>","You might notice that my website uses Flickr to display most of the images. This is done by using an iframe of the Flickr slideshow. The reason I chose to use Flickr to display my images is that I didn’t have much experience coding at the time (I’m still not a code genius). It was […]","flickr-changes-to-free-accounts",["Date","2018-11-22T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":821,"excerpt":822,"slug":825,"date":826,"modified":827,"readingTime":158,"acf":828,"tags":829,"author":18,"featuredMediaUrl":830,"featuredMedia":831},"Logo design for a Web Developer",{"raw":823,"plain":824},"\u003Cp>My first project at university was to write a report on how I designed a set of logos to brand myself as a web developer. We had to include research, feedback and final designs. I got a C1 on this report as I found it quite hard being the first one I&#8217;ve done at university. [&hellip;]\u003C\u002Fp>","My first project at university was to write a report on how I designed a set of logos to brand myself as a web developer. We had to include research, feedback and final designs. I got a C1 on this report as I found it quite hard being the first one I’ve done at university. […]","logo-design-for-a-web-developer",["Date","2018-11-09T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],"https:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002Fbfc46bfee4b29cd76ba47fce9df8298461166dcf1625803ae0fda5cf85803866\u002Fbfc46bfee4b29cd7-1Artboard-1joe.png","https:\u002F\u002Fjoebailey.xyz\u002Fcdn-cgi\u002Fimage\u002Fformat=auto,quality=80,fit=scale-down,width=1200\u002Fhttps:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002Fbfc46bfee4b29cd76ba47fce9df8298461166dcf1625803ae0fda5cf85803866\u002Fbfc46bfee4b29cd7-1Artboard-1joe.png",{"title":833,"excerpt":834,"slug":837,"date":838,"modified":839,"readingTime":158,"acf":840,"tags":841,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"rankingCoach",{"raw":835,"plain":836},"\u003Cp>I attended a workshop in Hotel Mercure Southampton Centre Dolphin on rankingCoach by Alasdair Stone. It was a very interesting event and we learnt about how to optimize website for search engines. We also learnt how to use tools such as Google My Business. I came away from the event with a lot of notes [&hellip;]\u003C\u002Fp>","I attended a workshop in Hotel Mercure Southampton Centre Dolphin on rankingCoach by Alasdair Stone. It was a very interesting event and we learnt about how to optimize website for search engines. We also learnt how to use tools such as Google My Business. I came away from the event with a lot of notes […]","rankingcoach",["Date","2018-11-08T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":843,"excerpt":844,"slug":847,"date":848,"modified":849,"readingTime":42,"acf":850,"tags":851,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Material Design",{"raw":845,"plain":846},"\u003Cp>“Make beautiful products, faster. Material is a design system – backed by open-source code – that helps teams build digital experiences” Material.io Material is a design language created by Google in 2014 for the Android platform. It has unified Android apps under a single design format. This has helped to give the Android platform a [&hellip;]\u003C\u002Fp>","“Make beautiful products, faster. Material is a design system – backed by open-source code – that helps teams build digital experiences” Material.io Material is a design language created by Google in 2014 for the Android platform. It has unified Android apps under a single design format. This has helped to give the Android platform a […]","material-design",["Date","2018-10-17T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":853,"excerpt":854,"slug":857,"date":858,"modified":859,"readingTime":158,"acf":860,"tags":861,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Barcamp Southampton",{"raw":855,"plain":856},"\u003Cp>I went to a Barcamp in Southampton, which consists of people giving talks on things they find interesting, with a heavy bias on technology. The normal talks last 45 minutes to an hour. They also have an opportunity to give lightning talks after lunch which last about 10 minutes and can be about anything. I [&hellip;]\u003C\u002Fp>","I went to a Barcamp in Southampton, which consists of people giving talks on things they find interesting, with a heavy bias on technology. The normal talks last 45 minutes to an hour. They also have an opportunity to give lightning talks after lunch which last about 10 minutes and can be about anything. I […]","barcamp-soton",["Date","2018-10-07T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":863,"excerpt":864,"slug":867,"date":868,"modified":869,"readingTime":42,"acf":870,"tags":871,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"CSS Grid",{"raw":865,"plain":866},"\u003Cp>&#8220;CSS Grid Layout is shaping up to be the layout tool we’ve always wanted on the web.&#8221; Stacy Kvernmo (September 19, 2016) In the early days of the web, developers laid things out using tables. Tables are only designed for tabular data and developers were splitting GIFs up to lay them out and create visually [&hellip;]\u003C\u002Fp>","“CSS Grid Layout is shaping up to be the layout tool we’ve always wanted on the web.” Stacy Kvernmo (September 19, 2016) In the early days of the web, developers laid things out using tables. Tables are only designed for tabular data and developers were splitting GIFs up to lay them out and create visually […]","css-grid",["Date","2018-10-04T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":873,"excerpt":874,"slug":877,"date":878,"modified":879,"readingTime":158,"acf":880,"tags":881,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Web Design and Development",{"raw":875,"plain":876},"\u003Cp>I&#8217;ve just started my first proper job in the Web Design and Development industry at Fluid Studios Ltd&nbsp;and it&#8217;s going really well. I&#8217;m also starting to get ready for university, the next big exciting chapter in my life.\u003C\u002Fp>","I’ve just started my first proper job in the Web Design and Development industry at Fluid Studios Ltd and it’s going really well. I’m also starting to get ready for university, the next big exciting chapter in my life.","web-design-and-development",["Date","2018-06-08T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":883,"excerpt":884,"slug":887,"date":888,"modified":889,"readingTime":158,"acf":890,"tags":893,"author":18,"featuredMediaUrl":894,"featuredMedia":895},"joebailey.xyz",{"raw":885,"plain":886},"\u003Cp>I&#8217;ve started creating a site to showcase my work as a web design professional. Once finished it will be live at joebailey.xyz I&#8217;m not really feeling it as much as my photography website, I don&#8217;t think the design flows that well, and it doesn&#8217;t really optimize for mobile all that well either. I think maybe [&hellip;]\u003C\u002Fp>","I’ve started creating a site to showcase my work as a web design professional. Once finished it will be live at joebailey.xyz I’m not really feeling it as much as my photography website, I don’t think the design flows that well, and it doesn’t really optimize for mobile all that well either. I think maybe […]","joebailey-xyz",["Date","2018-03-21T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":891},[559,294,892],"Netlify",[],"https:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002Fe28b08c407668af2eb67e835a3986a145fd2c94feace17010d6a8d860d780278\u002Fe28b08c407668af2-Logo.jpg","https:\u002F\u002Fjoebailey.xyz\u002Fcdn-cgi\u002Fimage\u002Fformat=auto,quality=80,fit=scale-down,width=1200\u002Fhttps:\u002F\u002Fcms.joebailey.xyz\u002Fmedia\u002Fe28b08c407668af2eb67e835a3986a145fd2c94feace17010d6a8d860d780278\u002Fe28b08c407668af2-Logo.jpg",{"title":897,"excerpt":898,"slug":901,"date":902,"modified":903,"readingTime":158,"acf":904,"tags":905,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"New Blog – Joe Bailey Photography",{"raw":899,"plain":900},"\u003Cp>I&#8217;ve finally started phase 3 of the redesign of my website. Phase one was to create it, learning lots of code along the way to prepare me for my University course, and also to give me the creative freedom that I hadn&#8217;t yet been able to achieve with various website builders. During phase 2 I [&hellip;]\u003C\u002Fp>","I’ve finally started phase 3 of the redesign of my website. Phase one was to create it, learning lots of code along the way to prepare me for my University course, and also to give me the creative freedom that I hadn’t yet been able to achieve with various website builders. During phase 2 I […]","new-blog",["Date","2018-03-21T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],{"title":907,"excerpt":908,"slug":911,"date":912,"modified":913,"readingTime":158,"acf":914,"tags":915,"author":18,"featuredMediaUrl":19,"featuredMedia":19},"Welcome – Joe Bailey Photography",{"raw":909,"plain":910},"\u003Cp>Hello everyone, Welcome to my new website. Sorry for the delay, it&#8217;s been a year in the making and there&#8217;s still much to do, like implement a store and add my backlog of images to the gallery. If you encounter any problems whilst browsing, please don&#8217;t hesitate to contact me, and I&#8217;ll see what I [&hellip;]\u003C\u002Fp>","Hello everyone, Welcome to my new website. Sorry for the delay, it’s been a year in the making and there’s still much to do, like implement a store and add my backlog of images to the gallery. If you encounter any problems whilst browsing, please don’t hesitate to contact me, and I’ll see what I […]","welcome",["Date","2018-01-20T00:00:00.000Z"],["Date","2026-10-22T00:00:00.000Z"],{"project_url":15,"github":15,"icons":15},[],1794611533218]