<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Christonit</title>
    <description>The latest articles on DEV Community by Christonit (@christonit).</description>
    <link>https://dev.to/christonit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1277623%2F3551864f-f4ec-4507-b9f3-aa2b928e0d67.png</url>
      <title>DEV Community: Christonit</title>
      <link>https://dev.to/christonit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/christonit"/>
    <language>en</language>
    <item>
      <title>AWS + JavaScript + WordPress = Fun Content Automation Strategies Using Artificial Intelligence</title>
      <dc:creator>Christonit</dc:creator>
      <pubDate>Sat, 04 Jan 2025 03:53:49 +0000</pubDate>
      <link>https://dev.to/christonit/aws-javascript-wordpress-fun-content-automation-strategies-using-artificial-intelligence-1gl1</link>
      <guid>https://dev.to/christonit/aws-javascript-wordpress-fun-content-automation-strategies-using-artificial-intelligence-1gl1</guid>
      <description>&lt;p&gt;Months ago, I began collaborating on a project all about AI generated content for a client focused on the tech sector. My role was mostly focused on setting up  SSG using &lt;strong&gt;WordPress&lt;/strong&gt; as a &lt;strong&gt;Headless CMS&lt;/strong&gt; for a &lt;strong&gt;Nuxt&lt;/strong&gt; Front-end. &lt;/p&gt;

&lt;p&gt;The client used to write articles a couple of times per week about different trends or situations affecting the sector, in the hope of increasing traffic to the site and its output of articles  he decided to use AI  to generate articles for him. &lt;/p&gt;

&lt;p&gt;After some time, with the right prompts  the client had pieces of information that were close to an exact match of a human written article, it is super difficult to spot  they are machine made.&lt;/p&gt;

&lt;p&gt;Sometime after  I moved to work on different features,  I would continuously get asked  one specific thing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ey, can you update the featured image for this article?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After  2 weeks of daily updating Posts I had a small eureka moment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8c6cs60j6kciid1cw5m1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8c6cs60j6kciid1cw5m1.gif" alt="Image description" width="500" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Why don't I &lt;strong&gt;automate&lt;/strong&gt; the featured image generation for these articles using &lt;strong&gt;Artifical Intelligence&lt;/strong&gt;?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We already automated post writing, &lt;strong&gt;why don't automate the featured images?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my free time,  I was experimenting with generative LLMs on my computer so I had a solid idea of more or less how to tackle this side-quest. I sent a message to the  client detailing what is the problem, what I want to do and what were going to be the advantages  and without having to do convincing, I got the green-lit to work on this feature and right away I went with my first step.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Architecting how the solution is going to look.
&lt;/h2&gt;

&lt;p&gt;Given that I had some exposure to running models locally I knew right away it was not feasible to self host those models. With that discarded I started  to play around APIs that generated images based on text prompts. &lt;/p&gt;

&lt;p&gt;Featured images consisted of 2 parts: the main composed graphic and a catchy tagline.&lt;/p&gt;

&lt;p&gt;The composed graphic would be some elements related to the article, arranged in a nice way with then some colors and textures with some blend modes applied to achieve some fancy effects following the branding.&lt;/p&gt;

&lt;p&gt;Taglines were short, 8-12 words sentences with a simple drop shadow under them.&lt;/p&gt;

&lt;p&gt;Based on my testing, I realized that pursuing the AI route for image generation wasn’t practical. The image quality didn’t meet expectations, and the process was too time-consuming to justify its use. Considering this would run as an AWS Lambda function, where execution time directly impacts costs.&lt;/p&gt;

&lt;p&gt;With that discarded, I went with Plan B:  mashing images and design assets together using JavaScript's Canvas API.&lt;/p&gt;

&lt;p&gt;Taking a deep look we had mainly 5 styles of simple posts, and around 4 types of textures and 3 of them using the same text alignment, style and position. After doing some math I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Hmm, If i take these 3 images,  grab 8 textures and play with blend-modes, I can get around post 24 variations&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Given that those 3 types of posts had the same text style it was practically one template.&lt;/p&gt;

&lt;p&gt;With that settled, I  moved to the Tagline Generator. I wanted to create a  tagline based on the content and title of the article. I decided to use ChatGPT’s API given that the company was already paying for it, and after some experimenting and promps tweaking, I had a very good MVP for my tagline generator.&lt;/p&gt;

&lt;p&gt;With the 2 hardest parts of the task figured out, I spent some time in Figma putting together the  diagram for the final architecture of my service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aiap4shvq1vk116upex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aiap4shvq1vk116upex.png" alt="Software Architecture Diagram" width="800" height="1175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2.Coding my lambda
&lt;/h2&gt;

&lt;p&gt;The plan was to create a Lambda function capable of analyzing post content, generating a tagline, and assembling a featured image—all seamlessly integrated with WordPress.&lt;/p&gt;

&lt;p&gt;I will provide some code but just enough to communicate the overall idea to ke.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analyzing the content
&lt;/h3&gt;

&lt;p&gt;The Lambda function starts by extracting the necessary parameters from the incoming event payload:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
const { title: request_title, content, backend, app_password} = JSON.parse(event.body);&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;title and content:&lt;/strong&gt; These provide the article’s context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;backend:&lt;/strong&gt; The WordPress backend URL for image uploads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;app_password:&lt;/strong&gt; The authentication token im going to use to upload as my user using Wordpress Rest API.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Generating the Tagline
&lt;/h3&gt;

&lt;p&gt;The function’s first major task is to generate a tagline using analyzeContent function,  which uses OpenAI’s API to craft a click-worthy tagline based on the article's title and content.&lt;/p&gt;

&lt;p&gt;Our function takes the post title and content but returns a tagline, post sentiment to know if post is a positive, negative or neutral opinion and an optional company symbol from the S&amp;amp;P index companies.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const { tagline, sentiment, company } = await analyzeContent({ title: request_title, content });&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This step is critical, as the tagline directly influences the image’s aesthetics.&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating the Featured Image
&lt;/h3&gt;

&lt;p&gt;Next, the generateImage function kicks in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let buffer;

buffer = await generateImage({
    title: tagline,
    company_logo: company_logo,
    sentiment: sentiment,
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designing the composition.&lt;/li&gt;
&lt;li&gt;Layering textures, colors, and branding elements.&lt;/li&gt;
&lt;li&gt;Applying effects and  creating the title.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a step by step breakdown on how it works:&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;generateImage&lt;/code&gt; function begins by setting up a blank canvas, defining its dimensions, and preparing it to handle all the design elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const COLOURS = {
        BLUE: "#33b8e1",
        BLACK: "#000000",
    }

    const __filename = fileURLToPath(import.meta.url);
    const __dirname = path.dirname(__filename);
    const images_path = path.join(__dirname, 'images/');
    const files_length = fs.readdirSync(images_path).length;
    const images_folder = process.env.ENVIRONMENT === "local"
        ? "./images/" : "/var/task/images/";


    registerFont("/var/task/fonts/open-sans.bold.ttf", { family: "OpenSansBold" });
    registerFont("/var/task/fonts/open-sans.regular.ttf", { family: "OpenSans" });


    console.log("1. Created canvas");

    const canvas = createCanvas(1118, 806);

    let image = await loadImage(`${images_folder}/${Math.floor(Math.random() * (files_length - 1 + 1)) + 1}.jpg`);


    let textBlockHeight = 0;

    console.log("2. Image loaded");

    const canvasWidth = canvas.width;
    const canvasHeight = canvas.height;
    const aspectRatio = image.width / image.height;


    console.log("3. Defined ASPECT RATIO",)

    let drawWidth, drawHeight;
    if (image.width &amp;gt; image.height) {
        // Landscape orientation: fit by width
        drawWidth = canvasWidth;
        drawHeight = canvasWidth / aspectRatio;
    } else {
        // Portrait orientation: fit by height
        drawHeight = canvasHeight;
        drawWidth = canvasHeight * aspectRatio;
    }

    // Center the image
    const x = (canvasWidth - drawWidth) / 2;
    const y = (canvasHeight - drawHeight) / 2;
    const ctx = canvas.getContext("2d");
    console.log("4. Centered Image")
    ctx.drawImage(image, x, y, drawWidth, drawHeight);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, a random background image is loaded from a predefined collection of assets. These images were curated to suit the tech-oriented branding while allowing for enough variety across posts. Background image is selected randomly based on its sentiment.&lt;/p&gt;

&lt;p&gt;To ensure each background image looked great, I calculated its dimensions dynamically based on the aspect ratio. This avoids distortions while keeping the visual balance intact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the Tagline
&lt;/h3&gt;

&lt;p&gt;The tagline is short but based on some rules, this  impactful sentence is split into manageable pieces and is styled dynamically to ensure it’s always readable, regardless of length or canvas size based on the word count for the line, word length, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log("4.1 Text splitting");
if (splitText.length === 1) {

    const isItWiderThanHalf = ctx.measureText(splitText[0]).width &amp;gt; ((canvasWidth / 2) + 160);
    const wordCount = splitText[0].split(" ").length;

    if (isItWiderThanHalf &amp;amp;&amp;amp; wordCount &amp;gt; 4) {

        const refactored_line = splitText[0].split(" ").reduce((acc, curr, i) =&amp;gt; {
            if (i % 3 === 0) {
                acc.push([curr]);
            } else {
                acc[acc.length - 1].push(curr);
            }
            return acc;
        }, []).map((item) =&amp;gt; item.join(" "));

        refactored_line[1] = "[s]" + refactored_line[1] + "[s]";

        splitText = refactored_line

    }
}

let tagline = splitText.filter(item =&amp;gt; item !== '' &amp;amp;&amp;amp; item !== '[br]' &amp;amp;&amp;amp; item !== '[s]' &amp;amp;&amp;amp; item !== '[/s]' &amp;amp;&amp;amp; item !== '[s]');
let headlineSentences = [];
let lineCounter = {
    total: 0,
    reduced_line_counter: 0,
    reduced_lines_indexes: []
}

console.log("4.2 Tagline Preparation", tagline);

for (let i = 0; i &amp;lt; tagline.length; i++) {
    let line = tagline[i];

    if (line.includes("[s]") || line.includes("[/s]")) {

        const finalLine = line.split(/(\[s\]|\[\/s\])/).filter(item =&amp;gt; item !== '' &amp;amp;&amp;amp; item !== '[s]' &amp;amp;&amp;amp; item !== '[/s]');

        const lineWidth = ctx.measureText(finalLine[0]).width
        const halfOfWidth = canvasWidth / 2;

        if (lineWidth &amp;gt; halfOfWidth &amp;amp;&amp;amp; finalLine[0]) {

            let splitted_text = finalLine[0].split(" ").reduce((acc, curr, i) =&amp;gt; {

                const modulus = finalLine[0].split(" ").length &amp;gt;= 5 ? 3 : 2;
                if (i % modulus === 0) {
                    acc.push([curr]);
                } else {
                    acc[acc.length - 1].push(curr);
                }
                return acc;
            }, []);

            let splitted_text_arr = []

            splitted_text.forEach((item, _) =&amp;gt; {
                let lineText = item.join(" ");

                item = lineText

                splitted_text_arr.push(item)
            })

            headlineSentences[i] = splitted_text_arr[0] + '/s/'

            if (splitted_text_arr[1]) {
                headlineSentences.splice(i + 1, 0, splitted_text_arr[1] + '/s/')
            }
        } else {
            headlineSentences.push("/s/" + finalLine[0] + "/s/")
        }


    } else {
        headlineSentences.push(line)
    }
}

console.log("5. Drawing text on canvas", headlineSentences);

const headlineSentencesLength = headlineSentences.length;
let textHeightAccumulator = 0;

for (let i = 0; i &amp;lt; headlineSentencesLength; i++) {
    headlineSentences = headlineSentences.filter(item =&amp;gt; item !== '/s/');
    const nextLine = headlineSentences[i + 1];
    if (nextLine &amp;amp;&amp;amp; /^\s*$/.test(nextLine)) {
        headlineSentences.splice(i + 1, 1);
    }

    let line = headlineSentences[i];

    if (!line) continue;
    let lineText = line.trim();

    let textY;

    ctx.font = " 72px OpenSans";

    const cleanedUpLine = lineText.includes('/s/') ? lineText.replace(/\s+/g, ' ') : lineText;
    const lineWidth = ctx.measureText(cleanedUpLine).width
    const halfOfWidth = canvasWidth / 2;

    lineCounter.total += 1

    const isLineTooLong = lineWidth &amp;gt; (halfOfWidth + 50);

    if (isLineTooLong) {

        if (lineText.includes(':')) {
            const split_line_arr = lineText.split(":")
            if (split_line_arr.length &amp;gt; 1) {
                lineText = split_line_arr[0] + ":";
                if (split_line_arr[1]) {
                    headlineSentences.splice(i + 1, 0, split_line_arr[1])
                }
            }
        }

        ctx.font = "52px OpenSans";

        lineCounter.reduced_line_counter += 1

        if (i === 0 &amp;amp;&amp;amp; headlineSentencesLength === 2) {
            is2LinesAndPreviewsWasReduced = true
        }


        lineCounter.reduced_lines_indexes.push(i)

    } else {

        if (i === 0 &amp;amp;&amp;amp; headlineSentencesLength === 2) {
            is2LinesAndPreviewsWasReduced = false
        }


    }

    if (lineText.includes("/s/")) {

        lineText = lineText.replace(/\/s\//g, "");

        if (headlineSentencesLength &amp;gt; (i + 1) &amp;amp;&amp;amp; i &amp;lt; headlineSentencesLength - 1 &amp;amp;&amp;amp; nextLine) {

            if (nextLine.slice(0, 2).includes("?") &amp;amp;&amp;amp; nextLine.length &amp;lt; 3) {
                lineText += '?';
                headlineSentences.pop();
            }

            if (nextLine.slice(0, 2).includes(":")) {
                lineText += ':';
                headlineSentences[i + 1] = headlineSentences[i + 1].slice(2);
            }

        }

        let lineWidth = ctx.measureText(lineText).width


        let assignedSize;


        if (lineText.split(" ").length &amp;lt;= 2) {

            if (lineWidth &amp;gt; (canvasWidth / 2.35)) {

                ctx.font = "84px OpenSansBold";

                assignedSize = 80

            } else {

                ctx.font = "84px OpenSansBold";

                assignedSize = 84

            }
        } else {


            if (i === headlineSentencesLength - 1 &amp;amp;&amp;amp; lineWidth &amp;lt; (canvasWidth / 2.5) &amp;amp;&amp;amp; lineText.split(" ").length === 3) {

                ctx.font = "84px OpenSansBold";
                assignedSize = 84

            } else {

                lineCounter.reduced_line_counter += 1;

                ctx.font = "52px OpenSansBold";
                assignedSize = 52

            }

            lineCounter.reduced_lines_indexes.push(i)

        }

        lineWidth = ctx.measureText(lineText).width



        if (lineWidth &amp;gt; (canvasWidth / 2) + 120) {

            if (assignedSize === 84) {
                ctx.font = "72px OpenSansBold";
            } else if (assignedSize === 80) {
                ctx.font = "64px OpenSansBold";

                textHeightAccumulator += 8
            } else {
                ctx.font = "52px OpenSansBold";
            }
        }



    } else {

        const textWidth = ctx.measureText(lineText).width


        if (textWidth &amp;gt; (canvasWidth / 2)) {
            ctx.font = "44px OpenSans";
            textHeightAccumulator += 12
        } else if (i === headlineSentencesLength - 1) {
            textHeightAccumulator += 12
        }

    }

    ctx.fillStyle = "white";
    ctx.textAlign = "center";

    const textHeight = ctx.measureText(lineText).emHeightAscent;

    textHeightAccumulator += textHeight;

    if (headlineSentencesLength == 3) {
        textY = (canvasHeight / 3)
    } else if (headlineSentencesLength == 4) {
        textY = (canvasHeight / 3.5)
    } else {
        textY = 300
    }

    textY += textHeightAccumulator;

    const words = lineText.split(' ');
    console.log("words", words, lineText, headlineSentences)
    const capitalizedWords = words.map(word =&amp;gt; {
        if (word.length &amp;gt; 0) return word[0].toUpperCase() + word.slice(1)
        return word
    });
    const capitalizedLineText = capitalizedWords.join(' ');

    ctx.fillText(capitalizedLineText, canvasWidth / 2, textY);

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the canvas is converted into a PNG buffer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const buffer = canvas.toBuffer("image/png");
return buffer;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Finally!!! Uploading the Image to WordPress
&lt;/h3&gt;

&lt;p&gt;After successfully generating the image buffer, the &lt;code&gt;uploadImageToWordpress&lt;/code&gt; function is called. &lt;/p&gt;

&lt;p&gt;This function handles the heavy lifting of sending the image to WordPress using its REST API by Encoding the Image for WordPress.&lt;/p&gt;

&lt;p&gt;The function first prepares the tagline for use as the filename by cleaning up spaces and special characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const createSlug = (string) =&amp;gt; {
    return string.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '');
};

const image_name = createSlug(tagline);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image buffer is then converted into a &lt;code&gt;Blob&lt;/code&gt; object to make it compatible with the WordPress API:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const file = new Blob([buffer], { type: "image/png" });&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Preparing the API Request Using the encoded image and tagline, the function builds a FormData object and I add optional metadata, such as alt_text for accessibility and a caption for context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;formData.append("file", file, image_name + ".png");
formData.append("alt_text", `${tagline} image`);
formData.append("caption", "Uploaded via API");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For authentication, the username and application password are encoded in Base64 and included in the request headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
const credentials = `${username}:${app_password}`;
const base64Encoded = Buffer.from(credentials).toString("base64");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sending the Image A POST request is made to the WordPress media endpoint with the prepared data and headers and after awaiting the response I validate for success or errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const response = await fetch(`${wordpress_url}wp-json/wp/v2/media`, {
    method: "POST",
    headers: {
        Authorization: "Basic " + base64Encoded,
        contentType: "multipart/form-data",
    },
    body: formData,
});

if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`Error uploading image: ${response.statusText}, Details: ${errorText}`);
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If successful, I return that same media &lt;code&gt;response&lt;/code&gt; in the lambda.&lt;/p&gt;

&lt;p&gt;This is how my lambda looks in the end.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { analyzeContent } from './ai-analysis.js';
import { uploadImageToWordpress, generateImage } from './draw.js';

export const handler = async (event) =&amp;gt; {
    try {
        const { title: request_title, content, backend, app_password, return_image } = JSON.parse(event.body);

        if (typeof request_title !== 'string' || typeof content !== 'string') {
            return {
                statusCode: 400,
                body: JSON.stringify({ error: 'Invalid input' }),
            };
        }

        const { tagline, company_logo, sentiment } = await analyzeContent({ title: request_title, content });

        const BACKENDS = ["https://content.x.com/", "https://content.y.com/", "https://content.z.com/"]

        let buffer;

        console.log("1. SUCCESSFUL Tagline Creation", { tagline, sentiment, backend }, BACKENDS.includes(backend));


        buffer = await generateImage({
            title: tagline,
            company_logo: company_logo,
            sentiment: sentiment,
        });

        console.log("3. SUCCESSFUL Image Creation for", backend);
        if (buffer) {

            const response = await uploadImageToWordpress(backend, buffer, tagline, app_password);

            if (response.status !== 201) {

                throw new Error("Image Upload Failed" + response.statusText);
            }

            const data = await response.json();

            console.log("4. SUCCESSFUL Image Upload for", backend);
            return {
                statusCode: 200,
                headers: {
                    'Content-Type': 'application/json',
                },
                body: data,
            };

        }
    } catch (error) {
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message }),
        };
    }
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a sample image produced by my script. It's not used in production, just created with generic assets for this example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbg4gio1zcfavwiabz7i8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbg4gio1zcfavwiabz7i8.png" alt="This is not a production used image, I used random elements for example purposes." width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Aftermath
&lt;/h2&gt;

&lt;p&gt;Some time has passed and everybody is happy that we no longer have shoddy or empty looking image-less articles, that images are a close match to the ones that the designer crafts,  the designer is happy that he gets to only focus on designing for other marketing efforts across the company.&lt;/p&gt;

&lt;p&gt;But then a new problem arose: sometimes the client did not like the Image generated and he would ask me to spin up my script to generate a new one for a specific post. &lt;/p&gt;

&lt;p&gt;This brought me to my next sidequest: A &lt;strong&gt;&lt;u&gt;Wordpress Plugin to Manually Generate a Featured image using Artificial Inteligence  for an Specific Post &lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>ai</category>
      <category>aws</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Case Study: Migrating a high-traffic WordPress website to a Headless Architecture using Nuxt</title>
      <dc:creator>Christonit</dc:creator>
      <pubDate>Fri, 16 Feb 2024 18:29:25 +0000</pubDate>
      <link>https://dev.to/christonit/case-study-moving-an-old-high-traffic-wordpress-website-to-nuxt-3190</link>
      <guid>https://dev.to/christonit/case-study-moving-an-old-high-traffic-wordpress-website-to-nuxt-3190</guid>
      <description>&lt;p&gt;In this case study, I'll share a small sneak peek of my experience of migrating a traditional WordPress site to a redesigned front-end and high-performance headless architecture using Nuxt. &lt;/p&gt;

&lt;p&gt;I’ll highlight our challenges, focus on what we think to be the most important and pivotal moments of our decision-making process, and the ultimate positive impact our move to Nuxt made on our lead generation website.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little bit of history
&lt;/h2&gt;

&lt;p&gt;I have been working for my client for almost 4 years, for the first 2 years all I was doing was marketing microsites and small experiments but that changed in the second quarter of  2022.&lt;/p&gt;

&lt;p&gt;A  highly visited website was around 12 years old and with the last redesign circa 2015, the whole website was showing its age. We use WordPress and a custom theme.&lt;/p&gt;

&lt;p&gt;The problems we had in 2022 were the same problems everybody running a business with WordPress might encounter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Technical Debt in the Form of Plugin Updates:&lt;/strong&gt; Our website was burdened by outdated plugins. which threatened to break our site with every attempt at an update.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Oversized Network Payload:&lt;/strong&gt; probably again because of out-of-use plugins that had to inject many dependencies into our website&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unnecessary Markup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Slow Website Load Times (sometimes)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We did try to solve the technical debt situation. Despite our best efforts to optimize the site, even disabling all tracking scripts in incognito mode to get a cleaner performance read, many pages stubbornly remained in the low 70s according to Lighthouse scores. For those unfamiliar, Lighthouse is a tool by Google that measures website quality across several metrics, including performance. Scores in the 70s indicate significant room for improvement, especially in terms of speed and user experience.&lt;/p&gt;

&lt;p&gt;Around April that’s when the Tech Director said &lt;strong&gt;“Ey, why don’t we go headless?”&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;I will try to be as faithful to the story as possible but the story (more or less) was this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Tech Stack.
&lt;/h2&gt;

&lt;p&gt;We were a small team back then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A front-end developer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Me. Another front-end developer with back then around 6 years of experience and a couple of years of experience using React and VueJs.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3 devs counting my boss. &lt;/p&gt;

&lt;p&gt;After the project started we were joined by an SEO specialist that had experience in these kinds of projects. He played a crucial role in providing UX quality assurance and in maintaining and reviewing the integrity of the information so SEO-wise everything ended up as close as possible to the back-then live site.&lt;/p&gt;

&lt;p&gt;We had never done anything similar with WordPress before: &lt;strong&gt;A website using WordPress as a headless CMS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had done some freelancing using React where I used Contentul, Strapi, or Builder.io to control the text, toggle components, site-wide announcements, and navigation for SPAs but not with an SEO, SSR, or SSG focus but anyway pitched the idea.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Well, why don't we go with NextJs or Gatsby? I have plenty of experience with React and many articles cover what we are trying to achieve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The conversation went somewhat like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Boss: Maybe later, let’s stick with VueJs. All 3 of us know it and there are others in the company that know it in case we need people to chime in.&lt;/p&gt;

&lt;p&gt;Me: Alright, I guess we are going with Nuxt then.&lt;/p&gt;

&lt;p&gt;Boss: No, we are going to use Gridsome, we are taking the Static Site Generation route.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Short-lived Gridsome Era
&lt;/h2&gt;

&lt;p&gt;I was excited. I had been reading about SSG for the past 12 months and was eager for a new challenge. The prospect of serving our pages with less latency and the best performance for the client and achieving above the 80s in the lighthouse sounded promising.&lt;/p&gt;

&lt;p&gt;When we researched about it, we found that it was a favorite tool in the Vue community for headless implementation because of how well it interacted with WordPress&lt;/p&gt;

&lt;p&gt;It had most of what was provided by a modern front-end framework plus nice interfaces to generate dynamic pages/templates from WordPress.&lt;/p&gt;

&lt;p&gt;It generated the graphQL data from WordPress without having to install plugins in our backend. Had good documentation on how to do middleware or load custom data when the server started.&lt;/p&gt;

&lt;p&gt;It did not matter however how much we liked Gridsome, reality soon set in with two major hurdles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance and Popularity:&lt;/strong&gt; The last update to Gridsome was in November 2020, leaving it behind with Vue 2's limitations, lacking the Composition API and Vite support. Its niche adoption meant solutions to our challenges were scarce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Handling:&lt;/strong&gt; Our decade-old site, boasting over published 9,000 posts, strained Gridsome's capabilities. Server start times and build processes became painstakingly long, with frequent crashes due to the overwhelming demand on our WordPress backend. &lt;/p&gt;

&lt;p&gt;Long story short, we began development around mid-April, and after shedding blood and tears, and facing the hurdles of the typical learning curve and the technical issues described above plus many more (that could make its list called “The Headless Wordpress with VueJS Cheat sheet”) we launched in September of 2022.&lt;/p&gt;

&lt;p&gt;A couple of months after we went live and even after many optimizations, just starting the server could take from 5 to 10 minutes. By the time we went live with Gridsome, our build times ended up being 4 to 6 minutes long after much optimization and content pruning a couple of months after launching. &lt;/p&gt;

&lt;p&gt;That meant that on every build, we generated almost 9k pages. And every page/post update on our WordPress backend triggered a new build.  And we had to run those builds to show the latest posts or content updates. More than once our builds would crash because our (now) WordPress backend couldn’t handle that many calls and requests back to back.&lt;/p&gt;

&lt;p&gt;You might ask “Why are you talking about WordPress to Gridsome if in the title you refer to &lt;strong&gt;‘Moving an old high-traffic WordPress website to Nuxt’?&lt;/strong&gt;”&lt;/p&gt;

&lt;p&gt;Because the first part of the tale on &lt;em&gt;&lt;strong&gt;how the coconut gets its water:&lt;/strong&gt;&lt;/em&gt;  was the gruesome rebuild we did.&lt;/p&gt;

&lt;p&gt;All the lessons learned there, the limitations, and the issues became the foundations and gave enough experience &amp;amp; proof to convince everybody that Nuxt is the way to go for the next batch of websites.&lt;/p&gt;

&lt;p&gt;Yes, this was supposed to be the first of many websites to transition to SSG, it was the prototype to benchmark to create the template on how to work the next step: the website’s redesign. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler alert&lt;/strong&gt;, the other websites were migrated to Nuxt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redesigning a WordPress Website using Nuxt
&lt;/h2&gt;

&lt;p&gt;Almost one year later after the Gridsome launch, we got the task of doing the facelift.&lt;/p&gt;

&lt;p&gt;Only this time, nobody was against using Nuxt.&lt;/p&gt;

&lt;p&gt;The first step we took was to list all the non-dynamic pages we were going to use and what was going to be dynamic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsh4uvybismew7lj52581.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsh4uvybismew7lj52581.png" alt="Nuxt + Wordpress Folder Structure" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second step was installing the WPGraphQL plugin, which allows us to fetch data using GraphQL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76evj8nt3oi05u92hs2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76evj8nt3oi05u92hs2d.png" alt="WPGraphQL plugin page" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This plugin allows us to create and test the queries visually without having lots of GraphQL knowledge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxtt9s463ux24f2vutq74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxtt9s463ux24f2vutq74.png" alt="How does the plugin page looks like in WordPress" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The data our website uses is rendered server side so we prepped our fetch function that we later are going to use on useAsyncData. This will prove beneficial later because we ended up reducing the Layout Content Shift significantly for our website, all the data that our above-the-fold components need is already available on mount.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce1d8gjyvyeue63tdo6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fce1d8gjyvyeue63tdo6e.png" alt="WordPress API Call Function Using GraphQL" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have different page types that need different information but a couple of them do share queries. That’s why I moved the small part of the queries to its own utility const file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5e6lf7dunj9x7mrgznq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5e6lf7dunj9x7mrgznq.png" alt="GraphQL queries utility file" width="800" height="1596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main benefit of using GraphQL is &lt;em&gt;efficiency&lt;/em&gt;: We can retrieve all the data required by your Nuxt.js blog in a single request, unlike REST, where we need to make multiple requests to different endpoints to fetch the same data. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spoiler alert:&lt;/strong&gt; This *efficiency benefit would become our bane, the core of a show-stopper a couple of weeks after our successful launch.&lt;/p&gt;

&lt;p&gt;A good idea is to make a Postman collection with all the API calls you need to do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fedhnjsnwouhu0qp8ea0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fedhnjsnwouhu0qp8ea0j.png" alt="Wordpress GraphQL + Rest Postman Collection Structure" width="544" height="996"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a couple of days of work, we had wired up most of the pages and components for our blog.  All that was left was to make public our sitemap for Google to crawl once the page went live and replace inside the post content all the references to internal pages that point to our backend to our current hostname.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;section&lt;/span&gt;
  &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contentFormatter(post.content.rendered)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember that we are using Nuxt as a front-end layer, we are going to end up pointing our host to our production domain and then make the WordPress instance point to a different domain. &lt;/p&gt;

&lt;p&gt;For this story, let's say our backend is going to end up called backend.wordpress.com.  &lt;/p&gt;

&lt;p&gt;For replacing the links, we used Cheerio. Cheerio allowed us to process our content as Nodelists and loop through our content and its tags.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentFormatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;decodeEntities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;xmlMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attribs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attribs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current_hostname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attribs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attribs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current_hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current_hostname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;(?!&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.(jpg|jpeg|png|webp|pdf|gif))&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gi&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attribs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attribs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extracted_slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attribs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.com/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;href&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current_hostname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;extracted_slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the sitemap, we did not find a plugin that allowed us to produce or directly download the ones provisioned by WordPress, and even if we found one, we still would have to change the references to posts using our backend URL.&lt;/p&gt;

&lt;p&gt;For this issue, we wrote a simple script that runs before our &lt;strong&gt;Nuxt&lt;/strong&gt; build that fetches the main sitemap from &lt;strong&gt;WordPress&lt;/strong&gt; and then loops its content to download more sitemaps. Will all the files in hand, I loop once again to replace our references to our backend for the current hostname. The current hostname would be the URL of where we deployed the website.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import required libraries&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;xml2js&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xml2js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;__dirname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current_hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CURRENT_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Define a function to fetch and process sitemap&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchSitemap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get the parent directory of the script file&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scriptParentDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Fetch the sitemap.xml and robots.txt from a given HOSTNAME defined in the .env file&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;sitemap.xml`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Parse the fetched sitemap.xml content&lt;/span&gt;
    &lt;span class="nx"&gt;xml2js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sitemaps_arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

      &lt;span class="c1"&gt;// Get the list of sitemaps from the parsed result&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sitemapList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sitemapindex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Iterate through sitemap items and modify URLs&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;sitemapList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetURL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
          &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;modifiedLoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetURL&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetURL&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nx"&gt;modifiedLoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;current_hostname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With all the references updated, I proceeded to write our files to our public folder for Google to crawl.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Launch
&lt;/h2&gt;

&lt;p&gt;In the first week of November 2023, we confidently pushed our project live, transitioning smoothly into operation without significant disruptions.&lt;/p&gt;

&lt;p&gt;However, &lt;em&gt;&lt;u&gt;"without major issues"&lt;/u&gt;&lt;/em&gt; came with a caveat. Many weeks after, the arrival of our Core Web Vitals (CWV) report revealed performance challenges that had lain dormant until the site was tested by real-world production demands and increased traffic. These insights prompted a focused effort to optimize our site’s performance post-launch and made us question &lt;a href="https://dev.to/christonit/how-to-improve-page-load-times-and-web-core-vitals-in-nuxt-websites-j3l"&gt;How do we improve Page Load Times and Web Core Vitals in Nuxt&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The aftermath
&lt;/h2&gt;

&lt;p&gt;3 months after launch, our work on the redesign is helping drive a 40% increase in visits and a 20% increase in session duration to the already impressive numbers.&lt;/p&gt;

&lt;p&gt;I hope this case study sheds light on the intricacies of such a migration and serves as a valuable resource for those considering a similar transition. The journey has been full of learning opportunities.&lt;/p&gt;

&lt;p&gt;Thank you for sticking with me to the very end.&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>vue</category>
      <category>wordpress</category>
      <category>headless</category>
    </item>
    <item>
      <title>How to Improve Page Load Times and Web Core Vitals in Nuxt Websites</title>
      <dc:creator>Christonit</dc:creator>
      <pubDate>Fri, 16 Feb 2024 18:28:32 +0000</pubDate>
      <link>https://dev.to/christonit/how-to-improve-page-load-times-and-web-core-vitals-in-nuxt-websites-j3l</link>
      <guid>https://dev.to/christonit/how-to-improve-page-load-times-and-web-core-vitals-in-nuxt-websites-j3l</guid>
      <description>&lt;p&gt;We embarked in a website redesign using Nuxt that spanned 2 months of hardwork before launch. After a couple of weeks with our new website live, it was time to run an user experience and core web vitals audit. When the reports came in, we had  2 tasks.&lt;/p&gt;

&lt;p&gt;How do we improve our web performance score to comply with  Lighthouse, specifically, FCP and Total Blocking time when navigating between posts and how do we decrease the page load time when navigatig between posts.&lt;/p&gt;

&lt;p&gt;After digging deep on our CWV reports and our codebase. I nailed down the main offenders of our bad performance scores and long load times when navigating between pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GraphQL queries for our flagship pages:&lt;/strong&gt; Depending on the information we needed to fetch, GraphQL would take longer to respond.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Too many requests&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Some iframes were not being correctly deferred&lt;/strong&gt; &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwthbuk4iqiuvrlp3026.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwthbuk4iqiuvrlp3026.png" alt="nuxt vs. wordpress"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these findings, I had 3 tactics that I could implement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- Accelerating Resource Loading with Preconnection&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;- Asynchronous creation and loading of Embeds&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;- Migrating the main post request from GraphQL to REST&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have to clarify, before going further, that already had best practices such as image optimization or lazy loading are already in place but these I'm explaining below are just tactics we weren't initially implementing. &lt;/p&gt;

&lt;h2&gt;
  
  
  1. Accelerating Resource Loading with Preconnect
&lt;/h2&gt;

&lt;p&gt;We use resources from many external domains such as google fonts and our content backend. When visiting a page as a new user, this often requires establishing connections to domains that are not initially connected to our main page's loading process. This connection time might mean a longer response time from the server.&lt;/p&gt;

&lt;p&gt;We tackled this problem by establishing network connections early to our font servers and our backend in our layout page.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"font"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.gstatic.com"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preconnect"&lt;/span&gt; &lt;span class="na"&gt;:href=&lt;/span&gt;&lt;span class="s"&gt;"hostname"&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This might mean less wait/queue time by the time we need to download those resources.&lt;/p&gt;

&lt;p&gt;Pre-connection for font downloading might not be the best example, but pre-connecting avoids getting a performance penalty from google because  font loading is something that could be render blocking or could induce cumulative layout shift.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Asynchronous  component creation and lazy loading of Embeds
&lt;/h2&gt;

&lt;p&gt;In some cases we have embeds that are most of the time above the fold, this means they execute too close to our initial Document load (DOMContentLoaded event), and we cannot defer them organically; they will initialize loading regardless of our intent to lazy load.&lt;/p&gt;

&lt;p&gt;This has a negative side effect on our performance in the way that when our embed begins to load, it's going to initialize downloading all its dependencies.&lt;/p&gt;

&lt;p&gt;In our case, on some pages, we have an embedded real-time stocks scanner that is its separate SPA (Single Page App) and most of the posts load a text-to-speed audio player.&lt;/p&gt;

&lt;p&gt;The result is that so close to our initial load, our page is expediting the request for many resources almost equivalent to the download of a new page that is not critical for our FCP. This meant a variable amount of time from 100ms to more than 1 second depending on how the other server/domain is loaded at the moment of our request. There also could be the case where all this data is downloaded and executed alongside other heavy processes on our website, which could slow down a user's computer.&lt;/p&gt;

&lt;p&gt;Google might penalize you by saying "You need to reduce your JS execution time or your initial payload is too big".&lt;/p&gt;

&lt;p&gt;To workaround this issue I took 2 approaches:&lt;/p&gt;

&lt;h3&gt;
  
  
  Timing Lazy Loading the Iframes
&lt;/h3&gt;

&lt;p&gt;Move the iframe to its out component and make its rendering conditional on the parent component.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!--&lt;/span&gt; &lt;span class="nx"&gt;Conditionally&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="nx"&gt;based&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showIframe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LazyTopGainerIframe&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;showIframe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showIframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;onMounted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Check if we are on the client-side before setting the timeout&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Use setTimeout to delay setting 'showIframe' to true&lt;/span&gt;
      &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;showIframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3-second delay&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Dynamically Create the Iframe
&lt;/h3&gt;

&lt;p&gt;Outside of our mounting lifecycle,  we create a function that creates the iframe and assign its src attribute or have the iframe in place but instead of loading the src straight up, we  create a data-src attribute and in our function we query for the iframe and switch data-src to src.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;embedScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;embedScript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://platform.example.com/widgets.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;embedScript&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;defer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;embedScript&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nf"&gt;onMounted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;useSidebarSorter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchLeftoverInformation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;addScript&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The most important part for this to work, is to make this function async and wrap our iframe generator or DOM query into a timeout we are awaiting. What this trick does is that  we can add enough time to wait for execution that we know for sure that the DOM and other more important resources for our FCP (First Contentful Paint) or LCP (Large Contentful Paint) like images or fonts have finished loading. Making instructions a promise  (async/await) makes our code concurrent, meaning waiting for the result of a certain function’s execution does not stop everything else from running.&lt;/p&gt;

&lt;p&gt;With this approach, I’d suggest the biggest offenders (the elements that might represent the biggest performance budget) be executed as late as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Migrating GraphQL to REST for our Nuxt - Headless Wordpress site
&lt;/h2&gt;

&lt;p&gt;I noticed that depending on how big the query, the server response time varies.  I played with the query a little bit and stripped it down multiple times to identify how much time everything takes to fetch. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfgh0xv727fvz1jfg3qq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfgh0xv727fvz1jfg3qq.png" alt="Postman collection audit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjs8qlhii8de53agho9fe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjs8qlhii8de53agho9fe.png" alt="Postman collection response time with GraphQL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But sadly the crucial information for our page load: post data such as title, id, SEO and Saswap Schema taxed too much the website.  &lt;/p&gt;

&lt;p&gt;That’s when I decided that the best course of action was to switch to rest for data fetching. Rest most of the time is 2x or 3x faster than GraphQL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fku6r8oqqxg505df3fxmh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fku6r8oqqxg505df3fxmh.png" alt="Rest API call Response Time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GraphQL allows us to query once for all the info we need but is slow, in this case a REST call brings most of the info we need to display the content above the fold. The only thing we need more calls for are the categories and comments.&lt;/p&gt;

&lt;p&gt;I got stressed a little bit in the beginning because REST doesn’t let us  fetch a post by slug and the comments don’t come in order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzgicweeow1b7nhmbhpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzgicweeow1b7nhmbhpa.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The solution I came up with was a script to fetch all the posts and then made a dictionary with the slug as its key with the POST ID as its property. &lt;/p&gt;

&lt;p&gt;Then I proceeded to create a small plugin to expose that information to our [..slug] page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5x1wqrrvegdmd1frryr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5x1wqrrvegdmd1frryr.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That way when we SPA Navigate, on our setup process I take the slug and search by that kay the POST ID on our Dictionary to do the REST call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fus0inqe6fp8hw4peorrl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fus0inqe6fp8hw4peorrl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Post comments, category information, tags and related posts are fetched with GraphQL in our mounted hook on client side because they are not crucial for our initial page load. Our Mounted hook is Async so the execution and process of a function don’t block other processes.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchLeftoverInformation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;graphql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
            query NewQuery {
              postBy(slug: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;") {
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;POST_CATEGORIES_QUERY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;TAGS_QUERY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;COMMENTS_QUERY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
              }
            }`&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postBy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postBy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postBy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postBy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;commentsCount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postBy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commentsCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// eslint-disable-next-line no-console&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nf"&gt;onMounted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchLeftoverInformation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You might wonder Why a dictionary and not saving the post information in a JSON and filtering by id? Because checking by index is always going to yield the same time versus looping to find a specific item in the array  where the time it takes to find an element grows linearly with the size of the array.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;The average document load time (DOMContentLoaded evnet)  and the full load for one of our most visited posts used to be 120 seconds and 150 seconds for full load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yoamayy685n95nsuy5c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yoamayy685n95nsuy5c.png" alt="Network Tab Before"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucnq8mfm5r0ft3caz00w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucnq8mfm5r0ft3caz00w.png" alt="Lighthouse Before"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwd2kdwor6148qng3koh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwd2kdwor6148qng3koh.png" alt="Network Tab After"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09anjcedsqxeavqw6rhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F09anjcedsqxeavqw6rhi.png" alt="Lighthouse Score After"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this rework, the initial load is averaging 66 seconds.&lt;/p&gt;

&lt;p&gt;These tactics helped achieve a wooping 45% decrease in our initial load time.&lt;/p&gt;

&lt;p&gt;And I hope dearly that if you stumbled upon this article and you are facing similar issues on your projects, these tactics help you reach  positive results as they have for me.&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>vue</category>
      <category>headless</category>
      <category>wordpress</category>
    </item>
  </channel>
</rss>
