<?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>Forem: Robert Marshall</title>
    <description>The latest articles on Forem by Robert Marshall (@robmarshall).</description>
    <link>https://forem.com/robmarshall</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%2F174191%2F5de7317a-c141-4d68-a8d6-b640e7139851.jpeg</url>
      <title>Forem: Robert Marshall</title>
      <link>https://forem.com/robmarshall</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/robmarshall"/>
    <language>en</language>
    <item>
      <title>Wispr Flow + Claude Transforms Content Creation</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Fri, 29 Aug 2025 09:24:58 +0000</pubDate>
      <link>https://forem.com/robmarshall/wispr-flow-claude-transforms-content-creation-787</link>
      <guid>https://forem.com/robmarshall/wispr-flow-claude-transforms-content-creation-787</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/wispr-flow-claude-transforms-content-creation/" rel="noopener noreferrer"&gt;https://robertmarshall.dev/blog/wispr-flow-claude-transforms-content-creation/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I kept hitting the same frustration every time I wanted to write about a technical project. I’d have this complex implementation in my head – all the edge cases, the gotchas, the lessons learned – but getting it from brain to readable article felt like translating between languages.&lt;/p&gt;

&lt;p&gt;So I’d procrastinate, jot down a few bullet points, then let the idea sit for weeks.&lt;/p&gt;

&lt;p&gt;The process was brutally slow – building content over weeks, adding bits here and there, tweaking and tailoring incrementally. It was subconscious-led rather than stream-of-thought-led.&lt;/p&gt;

&lt;p&gt;Then I discovered Wispr Flow + Claude, and everything changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers Don’t Lie
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://api.robertmarshall.dev/wp-content/uploads/2025/08/New-Project-2025-08-14T101543.302-1.jpg" rel="noopener noreferrer"&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%2F7zpmg3de6akr3vtaqexb.jpg" width="700" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s the baseline math: Average typing speed is 40 words per minute. Average speaking speed is 125-150 words per minute. That’s a 3-4x speed increase just on raw output.&lt;/p&gt;

&lt;p&gt;But the real multiplier is even higher because of the cognitive load difference. When you’re typing, you’re constantly self-editing and hesitating. When you’re speaking, you can just flow.&lt;/p&gt;

&lt;p&gt;But this isn’t really about typing versus talking. It’s about completely changing how we approach technical content creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Breakthrough Moment
&lt;/h2&gt;

&lt;p&gt;My aha moment came when I was using Wispr Flow with Claude for coding. I found myself speaking to the terminal, getting functionality specs out of my head, talking through features and edge cases (I wrote about this here, if you are interested: &lt;a href="https://robertmarshall.dev/blog/turning-claude-code-into-a-development-powerhouse/" rel="noopener noreferrer"&gt;Turning Claude Code into a Development Powerhouse&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I could think out loud and flow with ideas. It was far quicker to get information out of my head than typing ever was.&lt;/p&gt;

&lt;p&gt;What this meant was I could build comprehensive functionality specs for code just by talking – before I even touched the keyboard. The only keys I touched were Ctrl+Windows (to trigger Wispr).&lt;/p&gt;

&lt;p&gt;It hit me that this was a natural fit. Since I’m already living in Markdown files and writing mostly about development work – how I solved problems, why certain tools work well – the same approach should work for article creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  This Isn’t Actually New (But It’s Better)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://api.robertmarshall.dev/wp-content/uploads/2025/08/New-Project-2025-08-14T102549.811-1.jpg" rel="noopener noreferrer"&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%2Flquy0jar5i4p40eilmro.jpg" alt="Phonautograph" width="700" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo from National Museum of American History&lt;/p&gt;

&lt;p&gt;Let’s be honest here – people have been recording their thoughts since the 1860s with the Phonautograph. And then the Dictaphone came along. The only difference was other humans would then write up dictations. That cognitive load, that time taken to write things down, was just passed on to another human.&lt;/p&gt;

&lt;p&gt;We got decent speech-to-text in the 1990s/2000s. But AI allows for far more increased speed and there’s less human requirement. I don’t need to hire someone to write down my ramblings. It can go straight to screen and then can use AI to tidy it up.&lt;/p&gt;

&lt;p&gt;What has changed the game for me is the combination: immediate voice capture + intelligent clean-up and organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Writing Problem
&lt;/h2&gt;

&lt;p&gt;You know this pain: you understand a concept deeply, but getting it out of your head and into a shape that another human can follow can be tricky. The blank page problem is real. You know what you want to say but organizing it feels impossible.&lt;/p&gt;

&lt;p&gt;This is where the voice-first approach shines. You can just start talking about the problem you’re trying to solve. Go around the houses a little bit. Mention edge cases as they occur to you. The AI will help sort it out later.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Current Workflow
&lt;/h2&gt;

&lt;p&gt;Here’s exactly how I do this now:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Minimal mental prep&lt;/strong&gt; : Just thinking “Is this a subject I want to cover? Can it be helpful?” I don’t fully plan out the article anymore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Voice dump&lt;/strong&gt; : I spit ball the idea to Claude (via Wispr Flow) and monologue about what I want to talk about. I go around the houses, mention tangents, explore different angles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. AI interview&lt;/strong&gt; : Here’s the key – I ask Claude to interview me. “Here’s the initial thing we’re trying to discuss, can you help me dig in?” Claude returns 3-4 questions that I can see on screen and work through systematically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Iterative exploration&lt;/strong&gt; : I can direct the questions or say “I’ve had a thought, let’s talk about this angle.” We dig deep and go heavy, knowing we can pull it back once we’ve got all the context out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Technical additions&lt;/strong&gt; : Once I’ve got all the conversation down, I manually add any code examples, snippets, or links. I want to make sure the code is correct – you can’t just ask AI to generate technical examples and trust them blindly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. AI clean-up with oversight&lt;/strong&gt; : I ask Claude to summarize, but then I take both the original context and the summary and run through a few iterations: “Is there anything we’ve missed? Is there anything that could enhance this direction?”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human refinement&lt;/strong&gt; : I come back days or weeks later with fresh eyes and tweak for flow, personality, and accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why The Interview Format Works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://api.robertmarshall.dev/wp-content/uploads/2025/08/New-Project-2025-08-14T102121.479-1.jpg" rel="noopener noreferrer"&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%2Fcq4nepbv4s8i3g8uk8uj.jpg" alt="Getting the ideas out of your head for content writing" width="700" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having Claude interview you breaks down content blocks that you might get, especially first thing in the morning when you need a prompt to get moving. You start with a generalized outline, then as questions come: “Oh, well what about this? Well, no that’s not what I want. What about this? Yeah, that’s the right direction.”&lt;/p&gt;

&lt;p&gt;The ability to keep being poked along – “What are your thoughts on this? What about that?” – really maintains flow. You probably could get there solo, but it would take much more time.&lt;/p&gt;

&lt;p&gt;In conversation, you can flip-flop back and forth, whereas when I write, it tends to be very structured, one trail of thought. Talking allows far more natural self-critique and counter-arguments in a fluid way. If there’s an additional thought tied to the last thought, you can briefly touch on it, then come back to the first thought. By getting it out of your brain and into Claude, you haven’t lost it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Clean-up Balance
&lt;/h2&gt;

&lt;p&gt;Here’s the critical warning: if you use Claude too much, if you take all your context and meandering thoughts and ask Claude to just summarize everything, Claude can be very aggressive with how it reduces that information. You can end up with a very lobotomized article that could otherwise be far more human.&lt;/p&gt;

&lt;p&gt;What I do is ask for summaries, but then cross-reference with the original context. “Is there anything we’ve missed? Is there anything that could enhance this direction?” But you need to drive it – if you let Claude drive completely, you lose your personality and style.&lt;/p&gt;

&lt;p&gt;Everyone has their own way of telling stories, their own pauses, their own mannerisms. If you’re too Claude-ified, it will remove your personality, and readers can tell the difference between a good writer working with AI and a fully AI piece of content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Type Considerations
&lt;/h2&gt;

&lt;p&gt;This approach works brilliantly for technical documentation, development articles, and explanatory content. As long as you can get everything out of your head initially, it’s not necessarily limited by content type.&lt;/p&gt;

&lt;p&gt;Where you need to hold back on AI involvement is when you start shaping that raw material into something specific. For creative writing like stories, this wouldn’t work well. But for technical documentation – especially dry documentation about processes – having an AI-driven clean-up process might actually be beneficial because you’re dealing with if-statements and cause-and-effect rather than emotions and emotional conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality Control for Technical Content
&lt;/h2&gt;

&lt;p&gt;When you’re explaining complex technical concepts, developers can’t afford inaccuracies in their documentation. Here’s my quality control process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human verification&lt;/strong&gt; : You still need to read everything back and tweak it. Mistakes happen even in human writing – your first draft needs editing regardless of how you created it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual code verification&lt;/strong&gt; : Add code snippets and examples manually after the AI clean-up. If you’re explaining how to do something with code, you need to ensure what you’re sharing is 100% correct and verified by yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsible writing balance&lt;/strong&gt; : There’s a clear line between what AI can do (organize thoughts, improve flow) and what you should do (verify technical accuracy, maintain authenticity).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started: The Minimum Viable Experiment
&lt;/h2&gt;

&lt;p&gt;If you are sceptical about adding AI to your writing workflow, here’s a small experiment you can run:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Wispr Flow&lt;/strong&gt; (free plan available) – &lt;a href="https://wisprflow.ai/" rel="noopener noreferrer"&gt;wisprflow.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Claude&lt;/strong&gt; (free plan available) – &lt;a href="https://claude.ai/" rel="noopener noreferrer"&gt;claude.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try this prompt&lt;/strong&gt; : “I want to write a technical article about [TOPIC]. Can you interview me to help me explore all the angles and get my thoughts organized? Start with broad questions about the problem I’m solving, then dig into implementation details and lessons learned.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Just start talking&lt;/strong&gt; into Wispr about your topic for 5-10 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Let Claude interview you&lt;/strong&gt; based on what you said&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;See what happens&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You could even start by just typing your article title into Claude and asking for an interview. It’s a good jump-off point that requires zero additional tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Meta-Validation
&lt;/h2&gt;

&lt;p&gt;This entire article was created using exactly this process. I started with a rough idea about wanting to write about Wispr Flow + Claude. I talked through my thoughts, Claude interviewed me about different angles, and we built up layers through conversation.&lt;/p&gt;

&lt;p&gt;You can see the process working in real-time: I discovered connections as I spoke (like connecting tangential speech to better self-critique), ideas got more sophisticated with each response, and we built enough raw material for a substantial article just through conversational exploration.&lt;/p&gt;

&lt;p&gt;We literally demonstrated the “getting more context out upfront” principle while creating content about that exact principle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Game Changer
&lt;/h2&gt;

&lt;p&gt;This isn’t just about speed, though the 3-4x output increase is significant. It’s about changing how you think about technical writing from a painful, time-consuming process to something that flows naturally from your existing knowledge.&lt;/p&gt;

&lt;p&gt;Instead of staring at a blank page wondering how to structure complex ideas, you can just start talking about the problem you solved. The structure emerges from the conversation. The AI helps with organization and clean-up, but the insights, the personality, the technical accuracy – that’s still all you.&lt;/p&gt;

&lt;p&gt;The cognitive load of writing is distributed across the right tools: your brain for insights and technical knowledge, your voice for natural expression, AI for organization, and your editing skills for final refinement and accuracy.&lt;/p&gt;

&lt;p&gt;It’s not about replacing human creativity or expertise. It’s about removing friction from the process of sharing that expertise with others.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>teaching</category>
      <category>trends</category>
    </item>
    <item>
      <title>Why My Gatsby to Next JS Migration Failed (And What It Taught Me About AI Workflows)</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Thu, 14 Aug 2025 12:32:52 +0000</pubDate>
      <link>https://forem.com/robmarshall/why-my-gatsby-to-next-js-migration-failed-and-what-it-taught-me-about-ai-workflows-2l9n</link>
      <guid>https://forem.com/robmarshall/why-my-gatsby-to-next-js-migration-failed-and-what-it-taught-me-about-ai-workflows-2l9n</guid>
      <description>&lt;p&gt;I recently tried to publish an article on this blog, and got hit with a number of Cloudflare errors. All stemming from the fact that I had forgotten that I have created the website with Gatsby many years ago.&lt;/p&gt;

&lt;p&gt;In past freelance jobs I have migrated, fixed, cleaned up and patched numerous Gatsby websites. It was painful and unforgiving. Not about to do that again… (especially as is a dead framework – development has essentially stopped and the ecosystem is stagnant).&lt;/p&gt;

&lt;p&gt;The plan was to migrate the project away. But I am crazy busy with paid work, so a manual migration was not on the cards.&lt;/p&gt;

&lt;p&gt;Claude Code is a pretty solid tool in the toolbox for me, so why not see if it can handle the whole thing…&lt;/p&gt;

&lt;h2&gt;
  
  
  The “Obvious” Approach
&lt;/h2&gt;

&lt;p&gt;My initial instinct was straightforward: write one prompt, outline the migration approach, break the work down into stages, use MD files to handle memory, and hope the sub agents could keep up.&lt;/p&gt;

&lt;p&gt;Arguably this is a small website, so there shouldn’t be too much needed… and I am using some pretty powerful tools within Claude Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tools
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Context7&lt;/strong&gt; – Pulls in live documentation and context for third-party libraries&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Serena&lt;/strong&gt; – Semantic code search and editing across the entire codebase&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Sequential Thinking&lt;/strong&gt; – Structured reasoning for complex decision making&lt;/p&gt;

&lt;p&gt;I wrote a piece about &lt;a href="https://robertmarshall.dev/blog/turning-claude-code-into-a-development-powerhouse/" rel="noopener noreferrer"&gt;this stack here&lt;/a&gt;, if you want more information on how it sits together.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Prompt
&lt;/h3&gt;

&lt;p&gt;The key was being explicit about the workflow and delegation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/go
You are looking at an old Gatsby JS project. Your job is to upgrade it to a Next JS App Router project. Some of the components will be able to be left as they are, due to being react. However some will need to be migrated to the Next way of doing things.
The first task is to use 1 subagent to look over the whole project and find a list of files that need to be updated. Once done it should save the list of files into a "migrate.md" file.
Once done, one agent should look over the "migrate.md" file and work out of there is any extra work needed. i.e. util functions, or underlaying Next JS code needed that multiple pages will use. This should be added to the top of the list with brief comment on how they should be used
Once done, these files outlined in at the top of the migrate.md file should be split between 2 sub agents, and should be created.
Once done, rest of the files outlined in the "migrate.md" file should be split between 4-6 sub agents. They should then handle the needed migration. Working through each file one by one.

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Actually Happened
&lt;/h2&gt;

&lt;p&gt;Claude replied with a solid plan in plan mode. It had correctly identified the WordPress headless CMS setup, mapped out the key Gatsby-specific pieces that needed replacing, and understood the dependency chain. The breakdown looked promising – it planned to tackle foundational infrastructure first, then components, then pages.&lt;/p&gt;

&lt;p&gt;45 minutes of Claude chuntering away. Divining, and pontificating.&lt;/p&gt;

&lt;p&gt;Watching its thinking process and the suggestions it was making (I had it set to automatically execute everything), it seemed like it had its head in the game.&lt;/p&gt;

&lt;p&gt;Unfortunately, not so much.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It All Went Wrong
&lt;/h2&gt;

&lt;p&gt;Here’s the problem: Gatsby’s complexity isn’t just in its components – it’s in all the custom resolvers, data transformation layers, and vendor-specific abstractions you need to make anything work. When Claude tried to “migrate” this mess, it didn’t simplify it. It recreated the complexity in Next.js.&lt;/p&gt;

&lt;p&gt;Claude built a whole collection of systems that sort of did the job, but made everything incredibly heavy. Multiple data fetching layers, complex resolvers that weren’t needed in Next.js, and abstractions on top of abstractions.&lt;/p&gt;

&lt;p&gt;In terms of migrating the components and styling – very very happy. This got everything off to the races. But the underlying architecture was a mess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: I Asked Claude to Solve the Wrong Thing
&lt;/h2&gt;

&lt;p&gt;After about 4 hours of trying to clean up the convoluted migration, I had a realization: &lt;strong&gt;I had asked Claude to migrate the wrong thing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of asking it to “upgrade this Gatsby project to Next.js,” I should have asked it to “help me build a clean Next.js foundation, then help me move specific pieces over.”&lt;/p&gt;

&lt;h2&gt;
  
  
  The Better Approach: Foundation First
&lt;/h2&gt;

&lt;p&gt;Here’s what I ended up doing, and what I should have done from the start:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Clean Slate Foundation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest my-new-blog --typescript --tailwind --eslint --app

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

&lt;/div&gt;



&lt;p&gt;Then prompted Claude specifically: “Create a clean Next.js blog architecture with WordPress as a headless CMS. Focus on modern best practices, simple data fetching, and clear separation of concerns.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: WordPress Data Layer Foundation
&lt;/h3&gt;

&lt;p&gt;Before touching any components, I needed a solid data layer. I prompted Claude: “Create a &lt;code&gt;lib/wordpress&lt;/code&gt; module that handles all WordPress API interactions. Include functions for fetching posts, categories, and individual post data. Set up proper caching with Next.js’s built-in cache, and create a revalidation webhook for when new posts are published.”&lt;/p&gt;

&lt;p&gt;This gave me clean, focused utilities like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;getAllPosts()&lt;/code&gt; with ISR caching&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getPostBySlug()&lt;/code&gt; with proper error handling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getCategories()&lt;/code&gt; for navigation&lt;/li&gt;
&lt;li&gt;Webhook endpoint for WordPress to trigger revalidation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having this foundation meant every subsequent component could use clean, Next.js-native data fetching instead of trying to recreate Gatsby’s complex GraphQL layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Component Migration
&lt;/h3&gt;

&lt;p&gt;With both the Next.js foundation and WordPress data layer in place, I could then ask Claude to migrate specific components one by one: “Take this Gatsby component and adapt it for Next.js App Router, using our WordPress utilities.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Integration &amp;amp; Testing
&lt;/h3&gt;

&lt;p&gt;Finally: “Wire up these converted components with our WordPress data layer and test the full flow.”&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Taught Me About AI Workflows
&lt;/h2&gt;

&lt;p&gt;The failure revealed something crucial about working with AI on complex tasks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI excels when it has a clear mental model of the target state.&lt;/strong&gt; When I asked Claude to migrate Gatsby to Next.js, it had to hold both architectures in its head simultaneously while trying to bridge them. When I gave it a clean Next.js project and asked it to add specific functionality, it had a single, clear target to work towards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt decomposition isn’t just about breaking tasks down – it’s about choosing the right problems to solve.&lt;/strong&gt; My original prompt was well-structured but fundamentally asked for the wrong thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foundation-first development works better with AI than migration-first.&lt;/strong&gt; Starting with &lt;code&gt;npx create-next-app&lt;/code&gt; gives Claude a proper mental model and established patterns to work with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Broader Lesson
&lt;/h2&gt;

&lt;p&gt;This experience changed how I approach complex AI-assisted development tasks. Instead of asking “How do I get from A to B?” I now ask “How do I build B properly, then move the best parts of A over?”&lt;/p&gt;

&lt;p&gt;It’s not just about AI either. How many times have we all spent weeks trying to upgrade or migrate legacy code when starting fresh would have been faster and cleaner?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Outcome
&lt;/h2&gt;

&lt;p&gt;After restarting with the foundation-first approach, the migration took about 4 hours total (including the cleanup time from my first attempt). The resulting codebase is clean, follows Next.js best practices, and actually performs better than the original Gatsby site.&lt;/p&gt;

&lt;p&gt;And this is the website you’re reading now.&lt;/p&gt;

&lt;p&gt;The key insight: Claude didn’t fail at the migration – I failed at defining the right problem to solve. Sometimes the best migration strategy is to not migrate at all.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gatsby</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Turning Claude Code into a Development Powerhouse</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Tue, 05 Aug 2025 12:14:04 +0000</pubDate>
      <link>https://forem.com/robmarshall/turning-claude-code-into-a-development-powerhouse-1njl</link>
      <guid>https://forem.com/robmarshall/turning-claude-code-into-a-development-powerhouse-1njl</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/turning-claude-code-into-a-development-powerhouse/" rel="noopener noreferrer"&gt;https://robertmarshall.dev/blog/turning-claude-code-into-a-development-powerhouse/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I've been using Claude Code for months now, and while it's solid, I kept hitting the same frustration: context switching hell. &lt;/p&gt;

&lt;p&gt;Claude would write decent code, but it was like working with a brilliant intern who'd never seen your codebase before. Then I discovered Model Context Protocols (MCPs) and everything changed. &lt;/p&gt;

&lt;p&gt;Not just "this is neat" - more like "ok, this is how AI coding should work". The real magic happens when you combine the right MCPs with proper prompting strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: AI with Amnesia
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://api.robertmarshall.dev/wp-content/uploads/2025/08/New-Project-100-1.jpg" rel="noopener noreferrer"&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%2F3plgt5fyr7h9orcv7lnt.jpg" alt="AI forgetting what it is doing" width="700" height="400"&gt;&lt;/a&gt; You're deep in a project, need to add authentication, and ask Claude for help. It spits out some generic OAuth implementation that completely ignores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The auth patterns you're already using elsewhere&lt;/li&gt;
&lt;li&gt;  The latest changes in the library you're working with&lt;/li&gt;
&lt;li&gt;  The architectural decisions your team made months ago&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you spend 20 minutes explaining context, then another 10 fixing the outdated API calls, then realize you're basically doing the work yourself anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: The MCP Stack
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://api.robertmarshall.dev/wp-content/uploads/2025/08/New-Project-2025-08-04T235408.808-1.jpg" rel="noopener noreferrer"&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%2Ft5o3fajld5w2yoy88dj5.jpg" alt="The best claude code MCP stack" width="700" height="467"&gt;&lt;/a&gt; After experimenting with different combinations, I landed on four tools that work together like a well-oiled machine:&lt;/p&gt;

&lt;h3&gt;
  
  
  Context7: Your Live Documentation Oracle
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://context7.com/" rel="noopener noreferrer"&gt;https://context7.com/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Pulls in real-time documentation for any third-party library &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; No more "wait, is this the old API?" moments&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude mcp add --transport sse context7 https://mcp.context7.com/sse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Context7 is basically having the maintainer of every library sitting next to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serena: Your Semantic Code Archaeologist
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/oraios/serena" rel="noopener noreferrer"&gt;https://github.com/oraios/serena&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Semantic search and editing across your entire codebase &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Finds patterns by intent, not just text matching&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena start-mcp-server --context ide-assistant --project $(pwd)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where things get interesting. Serena doesn't just grep your code - it understands what your code &lt;em&gt;does&lt;/em&gt;. Ask it "where do we handle user authentication?" and it'll find all the relevant pieces, even if they don't share the same variable names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequential Thinking: Your AI Architect
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@modelcontextprotocol/server-sequential-thinking" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@modelcontextprotocol/server-sequential-thinking&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Structured reasoning for complex technical decisions &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Breaks down hairy problems into logical, debuggable steps&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude mcp add sequential-thinking -s local -- npx -y @modelcontextprotocol/server-sequential-thinking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sequential Thinking is like having a senior architect who actually thinks before coding. &lt;/p&gt;

&lt;p&gt;Should you refactor or rebuild? What are the dependencies? What could go wrong? It maps out the decision tree so you can see the reasoning. &lt;/p&gt;

&lt;p&gt;Some people I spoken to say this is no longer needs as Claude does this internally. I currently don't agree (update pending), and this has helped break down problems numerous times.&lt;/p&gt;

&lt;h3&gt;
  
  
  WisprFlow: Your Voice-to-Code Pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wisprflow.ai/" rel="noopener noreferrer"&gt;https://wisprflow.ai/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Real-time transcription that's better than anything else out there &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Talk directly to Claude instead of typing out complex requirements Arguably, this is not a MCP. But it ties them all up together very nicely. &lt;/p&gt;

&lt;p&gt;This is the game-changer for speed. Rather than typing out the scope, I can talk directly to Claude. This increase in speed means faster iterations on code, and scope out different angles in the planning phase before jumping into the code. &lt;/p&gt;

&lt;p&gt;Far more context and useful information for the AI to work with.&lt;/p&gt;

&lt;h2&gt;
  
  
  And Then the Slash
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://api.robertmarshall.dev/wp-content/uploads/2025/08/New-Project-2025-08-04T235712.289-1.jpg" rel="noopener noreferrer"&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%2Fj8iwnwbs6wmrtjsd6whl.jpg" alt="The slash command" width="700" height="400"&gt;&lt;/a&gt; The breakthrough came when I created this slash command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Always use:

- serena for semantic code retrieval and editing tools
- context7 for up to date documentation on third party code
- sequential thinking for any decision making

Read the claude.md root file before you do anything.

#$ARGUMENTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before adding any prompt, now type "/go". As in: &lt;code&gt;/go add a [feature spec here]&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example: Building Auth from Scratch
&lt;/h2&gt;

&lt;p&gt;Let me show you how this actually works. Last week I needed to add OAuth to an existing Next.js app. Here's what happened: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Old Way (30 minutes):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Ask Claude for OAuth implementation&lt;/li&gt;
&lt;li&gt; Get generic code that doesn't match our patterns&lt;/li&gt;
&lt;li&gt; Explain our auth architecture&lt;/li&gt;
&lt;li&gt; Fix outdated API references&lt;/li&gt;
&lt;li&gt; Adapt to our component structure&lt;/li&gt;
&lt;li&gt; Debug integration issues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The New Way (8 minutes):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Sequential Thinking mapped out the requirements and dependencies&lt;/li&gt;
&lt;li&gt; Serena found our existing auth patterns and user management code&lt;/li&gt;
&lt;li&gt; Context7 pulled the latest NextAuth.js documentation&lt;/li&gt;
&lt;li&gt; Claude synthesized a complete implementation that fit perfectly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The difference isn't just speed - it's quality. The new implementation followed our existing patterns, used current best practices, and integrated seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Compound Effect
&lt;/h2&gt;

&lt;p&gt;Here's what I've noticed after a month of using this setup: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time savings:&lt;/strong&gt; 60-70% reduction on complex features &lt;br&gt;
&lt;strong&gt;Quality improvements:&lt;/strong&gt; Fewer bugs, better patterns, consistent architecture &lt;br&gt;
&lt;strong&gt;Mental overhead:&lt;/strong&gt; Way less context switching and "wait, how did we do this before?" &lt;/p&gt;

&lt;p&gt;But the biggest win? I'm not babysitting the AI anymore. I can give it complex, multi-step tasks and trust it to figure out the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  But Create a Scope...
&lt;/h2&gt;

&lt;p&gt;The reason why I included WisprFlow in the stack, is that AI is not a point and shoot - one button press approach. &lt;/p&gt;

&lt;p&gt;To make sure that the code you are writing is decent quality and structured correctly (not 20 new utility functions that were added for no reason, plus some weird transform) you need to give the machine a property documented plan. &lt;/p&gt;

&lt;p&gt;By talking through what you want to achieve with Claude Code in plan mode, you can get everything out of your head. Then you can read it back. Tweak it until the scope is bang on, and then break it into sections. &lt;/p&gt;

&lt;p&gt;Break it up yourself. Like you would if you are planning out an actual project. What gives you less of a headache also gives AI less of a headache. &lt;/p&gt;

&lt;p&gt;Spend the time upfront. Then let the stack take it away.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>texttovoice</category>
      <category>development</category>
    </item>
    <item>
      <title>How to Use Hookdeck.com to Test and Debug Webhooks</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Sat, 02 Aug 2025 07:27:13 +0000</pubDate>
      <link>https://forem.com/robmarshall/how-to-use-hookdeckcom-to-test-and-debug-webhooks-2o20</link>
      <guid>https://forem.com/robmarshall/how-to-use-hookdeckcom-to-test-and-debug-webhooks-2o20</guid>
      <description>&lt;p&gt;With the introduction of more easily accessible no-code tools like N8N, webhooks have become a "must-have" when deciding which service to choose. But then you have to test what they return! This piece aims to help with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  What Is a Webhook and How Does It Work?
&lt;/li&gt;
&lt;li&gt;  Real-World Webhook Examples

&lt;ul&gt;
&lt;li&gt;  FormBear
&lt;/li&gt;
&lt;li&gt;  Stripe
&lt;/li&gt;
&lt;li&gt;  GitHub
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  Testing Webhooks with Hookdeck: Step-by-Step

&lt;ul&gt;
&lt;li&gt;  Step 1: Set Up Your Hookdeck Project
&lt;/li&gt;
&lt;li&gt;  Step 2: Create an Event Source
&lt;/li&gt;
&lt;li&gt;  Step 3: Get Your Webhook URL
&lt;/li&gt;
&lt;li&gt;  Step 4: Send Test Webhooks
&lt;/li&gt;
&lt;li&gt;  Step 5: Monitor and Debug
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  Advanced Hookdeck Features
&lt;/li&gt;

&lt;li&gt;  Conclusion
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Webhooks are the backbone of modern web applications, enabling real-time communication between services. But testing and debugging them can be a nightmare, especially when you're dealing with third-party services, local development environments, or unreliable network connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Webhook and How Does It Work?
&lt;/h2&gt;

&lt;p&gt;A webhook is essentially a "reverse API call" - instead of your application polling another service for updates, the external service pushes data to your application when something interesting happens. Think of it like this: instead of constantly asking "Are we there yet?" on a road trip, webhooks are like getting a text message when you arrive at your destination. Here's the typical webhook flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Setup&lt;/strong&gt;: You provide a webhook URL to an external service&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Event occurs&lt;/strong&gt;: Something happens in the external service (user signs up, payment processed, form submitted)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Notification&lt;/strong&gt;: The service sends an HTTP POST request to your webhook URL with event data&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Processing&lt;/strong&gt;: Your application receives and processes the webhook payload&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This real-time communication pattern is crucial for building responsive applications that need to react immediately to external events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Webhook Examples
&lt;/h2&gt;

&lt;p&gt;Let's look at some common webhook use cases that you'll encounter as a developer:&lt;/p&gt;

&lt;h3&gt;
  
  
  FormBear
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://formbear.io/" rel="noopener noreferrer"&gt;FormBear is a modern form management platform&lt;/a&gt; that sends webhooks whenever forms are submitted. This is perfect for triggering automated workflows like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Sending welcome emails to new subscribers&lt;/li&gt;
&lt;li&gt;  Creating leads in your CRM&lt;/li&gt;
&lt;li&gt;  Triggering Slack notifications for support requests&lt;/li&gt;
&lt;li&gt;  Updating analytics dashboards in real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a user submits a FormBear form, you'll receive a webhook payload like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "id": "b8bf41bf-d634-41e1-bad2-a50754045215",
  "data": {
    "fields": {
      "name": "Sarah Johnson",
      "email": "sarah@example.com",
      "message": "I'd love to learn more about your services!"
    },
    "submission_id": "sub_1753966250344"
  },
  "form": {
    "id": "contact-form-123",
    "name": "Contact Form"
  },
  "event": "form.submitted",
  "metadata": {
    "ip_address": "192.168.1.100",
    "user_agent": "Mozilla/5.0...",
    "submitted_at": "2025-01-15T14:30:45.344Z"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stripe
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; is the gold standard for payment processing, and their webhooks are crucial for building reliable payment flows. Stripe sends webhooks for dozens of different events, but some of the most important ones include: &lt;strong&gt;Payment Events&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;invoice.payment_succeeded&lt;/code&gt; - A payment was successfully processed&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;invoice.payment_failed&lt;/code&gt; - A payment attempt failed&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;payment_intent.succeeded&lt;/code&gt; - One-time payment completed successfully&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Subscription Events&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;customer.subscription.created&lt;/code&gt; - New subscription started&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;customer.subscription.updated&lt;/code&gt; - Subscription plan changed&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;customer.subscription.deleted&lt;/code&gt; - Subscription canceled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Customer Events&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;customer.created&lt;/code&gt; - New customer account created&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;customer.updated&lt;/code&gt; - Customer information changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a real example of an &lt;code&gt;invoice.payment_succeeded&lt;/code&gt; webhook that Stripe would send when a customer successfully pays their subscription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "object": {
    "id": "in_1Rql02frY8V0rQXKfgb",
    "object": "invoice",
    "account_name": "Your Stripe Account",
    "amount_due": 4900,
    "amount_paid": 4900,
    "amount_remaining": 0,
    "billing_reason": "subscription_create",
    "currency": "gbp",
    "customer": "cus_Sm48uvqLyN",
    "customer_email": "hello@yourdomain.dev",
    "status": "paid",
    "lines": {
      "data": [
        {
          "id": "il_1RqlrYQCM8ViOeXS",
          "amount": 4900,
          "currency": "gbp",
          "description": "1 × Pro (at £49.00 / month)",
          "period": {
            "end": 1756603816,
            "start": 1753925416
          }
        }
      ]
    },
    "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1RqLqYQCM8V0/test_...",
    "invoice_pdf": "https://pay.stripe.com/invoice/acct_1RqLqXQCM8V0/test_.../pdf"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This webhook tells you that customer &lt;code&gt;hello@yourdomain.dev&lt;/code&gt; just successfully paid £49.00 for a Pro subscription. In your application, you might use this to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Upgrade the user's account&lt;/strong&gt; to Pro features immediately&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Send a confirmation email&lt;/strong&gt; with the invoice PDF link&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Update your analytics&lt;/strong&gt; to track monthly recurring revenue&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Sync with your CRM&lt;/strong&gt; to mark the customer as an active subscriber&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Grant access&lt;/strong&gt; to premium features or content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The beauty of Stripe webhooks is their reliability - even if your server is down when the payment processes, Stripe will keep retrying the webhook delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; webhooks are the backbone of modern DevOps workflows, automatically triggering actions when code changes occur. The most common webhook is the &lt;code&gt;push&lt;/code&gt; event, which fires whenever commits are pushed to a repository. The &lt;code&gt;push&lt;/code&gt; webhook is incredibly detailed, containing information about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;What changed&lt;/strong&gt;: Before/after commit SHAs and the full diff&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Who made the change&lt;/strong&gt;: Author and pusher information&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Where it happened&lt;/strong&gt;: Repository, branch, and organization details&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Commit details&lt;/strong&gt;: All commits in the push (up to 2048 commits)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what a typical &lt;code&gt;push&lt;/code&gt; webhook payload looks like when someone pushes new code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "ref": "refs/heads/main",
  "before": "6113728f27ae82b1a177c8d03f9e96edf246",
  "after": "0d1a26e67d8f5e1f6ba5c57fc3c71ac0fd1c",
  "created": false,
  "deleted": false,
  "forced": false,
  "base_ref": null,
  "compare": "https://github.com/octocat/Hello-World/compare/6113728f27ae...0d1a26e67d8f",
  "commits": [
    {
      "id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
      "tree_id": "f9d2a07e9488b91af2641b26b9407fe22a451433",
      "message": "Fix user authentication bug",
      "author": {
        "name": "Sarah Johnson",
        "email": "sarah@example.com",
        "username": "sarahj"
      },
      "url": "https://github.com/octocat/Hello-World/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
      "added": ["src/auth.js"],
      "removed": [],
      "modified": ["README.md", "package.json"]
    }
  ],
  "head_commit": {
    "id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
    "message": "Fix user authentication bug",
    "author": {
      "name": "Sarah Johnson",
      "email": "sarah@example.com"
    }
  },
  "repository": {
    "id": 1296269,
    "name": "Hello-World",
    "full_name": "octocat/Hello-World",
    "html_url": "https://github.com/octocat/Hello-World",
    "default_branch": "main"
  },
  "pusher": {
    "name": "sarahj",
    "email": "sarah@example.com"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This webhook tells you that Sarah Johnson just pushed a commit to the &lt;code&gt;main&lt;/code&gt; branch that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Fixed a user authentication bug&lt;/li&gt;
&lt;li&gt;  Added a new &lt;code&gt;src/auth.js&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;  Modified the README and package.json files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, you'd use this webhook to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Trigger CI/CD pipelines&lt;/strong&gt;: Automatically run tests, build the app, and deploy to staging&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Update project management&lt;/strong&gt;: Create deployment tickets or update issue status&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Send notifications&lt;/strong&gt;: Alert the team in Slack about new deployments&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Run security scans&lt;/strong&gt;: Automatically scan new code for vulnerabilities&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Update documentation&lt;/strong&gt;: Regenerate API docs or deployment guides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The beauty of GitHub's webhook system is that it gives you enough detail to make intelligent decisions about what actions to take based on the specific changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Webhooks with Hookdeck: Step-by-Step
&lt;/h2&gt;

&lt;p&gt;Let's walk through setting up Hookdeck to test webhooks from FormBear (you can apply these same steps to any webhook provider).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Set Up Your Hookdeck Project
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;a href="https://hookdeck.com" rel="noopener noreferrer"&gt;hookdeck.com&lt;/a&gt; and create a free account. You'll land on the getting started page where you can create your first connection. The interface is clean and intuitive - you'll see options to create connections for routing events through Hookdeck's infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create an Event Source
&lt;/h3&gt;

&lt;p&gt;Click "Create a connection" to set up your first webhook source. You'll need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Choose Source Type&lt;/strong&gt;: Select "Webhook" since we're receiving HTTP requests from FormBear&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Name Your Source&lt;/strong&gt;: Use a descriptive name like "formbear-prod" or "contact-form-webhooks"&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Configure Authentication&lt;/strong&gt;: Leave this disabled for now (you can add signature verification later)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The source represents the producer of your webhooks - in this case, FormBear's form submission events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Get Your Webhook URL
&lt;/h3&gt;

&lt;p&gt;Once you create the source, Hookdeck generates a unique URL for receiving webhooks. It will look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://hkdk.events/x31mwm3dg0tje9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This URL is what you'll provide to FormBear (or any other service) to start receiving webhooks. Copy this URL - you'll need it in the next step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Send Test Webhooks
&lt;/h3&gt;

&lt;p&gt;Now let's configure FormBear to send webhooks to your Hookdeck URL:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Log into FormBear&lt;/strong&gt;: Navigate to your FormBear dashboard&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Go to Webhooks&lt;/strong&gt;: Click on the "Webhooks" section in the sidebar&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Create New Webhook&lt;/strong&gt;: Click "Create Webhook" or similar button&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Configure the Webhook&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;URL&lt;/strong&gt;: Paste your Hookdeck URL&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Events&lt;/strong&gt;: Select "form.submitted" and any other events you want to monitor&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Name&lt;/strong&gt;: Give it a descriptive name like "Hookdeck Test"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Test the Webhook&lt;/strong&gt;: Use FormBear's built-in test feature to send a sample webhook&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;You'll need a FormBear account to follow along exactly, but you can use any webhook-enabled service or even send manual POST requests with curl to test the same concepts.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Monitor and Debug
&lt;/h3&gt;

&lt;p&gt;Return to your Hookdeck dashboard and you should see the webhook request appear in your "Requests" view. Hookdeck provides detailed information about each request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Request Details&lt;/strong&gt;: Method, headers, query parameters&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Payload&lt;/strong&gt;: Full JSON body with syntax highlighting&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Response&lt;/strong&gt;: Status codes and response bodies&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Timing&lt;/strong&gt;: Request duration and retry attempts&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Routing&lt;/strong&gt;: Which destinations received the webhook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can click on any request to see complete details, making debugging much easier than parsing server logs.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>formbear</category>
      <category>stripe</category>
      <category>n8n</category>
    </item>
    <item>
      <title>Migrating from query-string to URLSearchParams</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Sat, 19 Aug 2023 12:27:13 +0000</pubDate>
      <link>https://forem.com/robmarshall/migrating-from-query-string-to-urlsearchparams-5276</link>
      <guid>https://forem.com/robmarshall/migrating-from-query-string-to-urlsearchparams-5276</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/migrating-from-query-string-to-urlsearchparams/" rel="noopener noreferrer"&gt;https://robertmarshall.dev/blog/migrating-from-query-string-to-urlsearchparams/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;In my quest to remove as many third-party packages as possible from mine and my clients websites, I have been working through each package and checking if there is a native replacement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/query-string" rel="noopener noreferrer"&gt;query-string&lt;/a&gt; is a super useful package for parsing URL parameters, but it now has a native replacement, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams" rel="noopener noreferrer"&gt;URLSearchParams&lt;/a&gt;. It can be used both in node and the browser.&lt;/p&gt;

&lt;p&gt;One of the reasons to remove query-string is because of the continual error:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm WARN deprecated querystring. The querystring API is considered Legacy. new code should use the URLSearchParams API instead.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The following functions should help with removing this package from your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stringify URL Params with URLSearchParams
&lt;/h2&gt;

&lt;p&gt;I previously used &lt;code&gt;query-string&lt;/code&gt; to take an object of arrays, and turn into a comma separated URL. The commas within the URL were not encoded.&lt;/p&gt;

&lt;p&gt;This turned out to be a little bit trickier with &lt;code&gt;URLSearchParams&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What used to be:&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="nx"&gt;queryString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;arrayFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;comma&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is now a bit longer:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stringifyParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&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;// Set up a new URLSearchParams object.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchParams&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;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Loop through the params object and append each key/value&lt;/span&gt;
    &lt;span class="c1"&gt;// pair to the URLSearchParams object.&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// If a key does not have a value, delete it.&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;searchParams&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;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="c1"&gt;// Convert the URLSearchParams object to a string.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchParamsString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Replace the encoded commas with commas.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedSearchParamsString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParamsString&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="sr"&gt;/%2C/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;return&lt;/span&gt; &lt;span class="nx"&gt;decodedSearchParamsString&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parsing with URLStringParams
&lt;/h2&gt;

&lt;p&gt;Parsing turned out to be a little easier.&lt;/p&gt;

&lt;p&gt;What used to be:&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="nx"&gt;queryString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;arrayFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;comma&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is now the following:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parseParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&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;// Set up a new URLSearchParams object using the string.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&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;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Get an iterator for the URLSearchParams object.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="c1"&gt;// Loop through the URLSearchParams object and add each key/value&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Split comma-separated values into an array.&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;value&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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="s1"&gt;,&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="k"&gt;else&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="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// If a key does not have a value, delete it.&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;delete&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;key&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;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These functions can be added to a util file within your JS project and imported when needed.&lt;br&gt;&lt;br&gt;
Hopefully this helped you, and if you have any questions you can reach me at: &lt;a href="https://twitter.com/robertmars" rel="noopener noreferrer"&gt;@robertmars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>On-Click Lazy Load YouTube Video Iframe in React</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Mon, 07 Aug 2023 14:18:21 +0000</pubDate>
      <link>https://forem.com/robmarshall/on-click-lazy-load-youtube-video-iframe-in-react-10en</link>
      <guid>https://forem.com/robmarshall/on-click-lazy-load-youtube-video-iframe-in-react-10en</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/on-click-lazy-load-video-iframe-in-react/"&gt;https://robertmarshall.dev/blog/on-click-lazy-load-video-iframe-in-react/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Videos are heavy. And the JavaScript that is used in the video player is also heavy.&lt;/p&gt;

&lt;p&gt;Speed matters on a website (you know this, its why you are here). And with most videos the JS for the player is generally not needed as soon as the page has loaded. In fact the only time the user needs the video player to be loaded is when they want to watch the it. I.E. At the point that the video is clicked.&lt;/p&gt;

&lt;p&gt;I wrote a short piece on &lt;a href="https://robertmarshall.dev/blog/lazy-load-youtube-video-iframe-show-on-scroll/"&gt;loading YouTube videos in React on scroll&lt;/a&gt;. However it didn’t consider waiting for the click – what if the video is above the fold? Or the user never watches the video?&lt;/p&gt;

&lt;p&gt;Why load it if they don’t need it. &lt;a href="https://web.dev/"&gt;Web.dev&lt;/a&gt; wrote an article on using &lt;a href="https://web.dev/iframe-lazy-loading/"&gt;loading=”lazy” with iframes&lt;/a&gt; which slightly helps – but some JS is still loaded.&lt;/p&gt;

&lt;p&gt;The best result would be to have nothing but a compressed poster image shown until the user wants to watch the video. No matter if it is above or below the fold.&lt;/p&gt;

&lt;p&gt;In this piece I will walk through how to defer an iframes load (and its contents) until a user had clicked a button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Render an image with the same ratio as the video iframe (YouTube, Vimeo etc) inside a button. When the button is clicked inject an iframe with the video which auto plays.&lt;/p&gt;

&lt;p&gt;Take a look at this CodeSandbox for a working example of &lt;a href="https://codesandbox.io/s/load-youtube-video-in-react-on-click-of5olh"&gt;how to load a YouTube video on click in React&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  YouTube Without Improvements
&lt;/h2&gt;

&lt;p&gt;YouTubes embed code with no alterations looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;iframe
    title="YouTube video player"
    src="https://www.youtube.com/embed/NY76mkzJT6o"
    width="1280"
    height="720"
    frameborder="0"
    allowfullscreen="allowfullscreen"
&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;span&gt;﻿&lt;/span&gt;&lt;span&gt;﻿&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This music video by &lt;a href="https://linktr.ee/Fwar"&gt;Fwar&lt;/a&gt;, a good friend of mine. If you like it you can &lt;a href="https://open.spotify.com/artist/55diSqDMEEpwm8OUMH8Nab"&gt;find more of his music on Spotify&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is fairly heavy. Lets work out what is needed to make this YouTube video lighter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;The solution to this would look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only a thumbnail is shown and loaded initially.&lt;/li&gt;
&lt;li&gt;Thumbnail image should use browser lazy loading.&lt;/li&gt;
&lt;li&gt;YouTube video iframe is deferred until user clicks thumbnail.&lt;/li&gt;
&lt;li&gt;There is no flicker between thumbnail being removed and YouTube video showing&lt;/li&gt;
&lt;li&gt;The video should play as soon as it had loaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing the Solution
&lt;/h2&gt;

&lt;p&gt;Firstly lets create a button and thumbnail to replace the iframe. This should be the same ratio as a YouTube video.&lt;/p&gt;

&lt;p&gt;Also, lets use the YouTube play button icon to show the user they can interact with the button.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button
  className={thumbnailButton}
  onClick={() =&amp;gt; {
    startTransition(() =&amp;gt; {
      setShowVideo(true);
    });
  }}
&amp;gt;
  &amp;lt;div className={videoInner}&amp;gt;
    &amp;lt;img
      alt="Fwar - Mushrooms video thumbnail"
      src="https://i.ytimg.com/vi/NY76mkzJT6o/maxresdefault.jpg"
      className={thumbnailImage}
      loading="lazy"
    /&amp;gt;
    &amp;lt;img
      alt="Play Video"
      src="https://upload.wikimedia.org/wikipedia/commons/b/b8/YouTube_play_button_icon_%282013%E2%80%932017%29.svg"
      loading="lazy"
      className={playIcon}
    /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now lets add the classes needed for this section. This is where the ratio comes in. A little bit of padding magic!&lt;/p&gt;

&lt;p&gt;For complete transparency, this is SCSS – it makes life a little easier. Take a look at &lt;a href="https://codesandbox.io/s/load-youtube-video-in-react-on-click-of5olh?file=/src/style.module.scss:0-640"&gt;the example&lt;/a&gt; to see how this fits together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.container {
  max-width: 500px;
  padding: 10px;
}

.videoRatio {
  overflow: hidden;
  padding: 56.25% 0 0 0;
  position: relative;
  width: 100%;
}

.videoInner {
  bottom: 0;
  height: 100%;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
  width: 100%;
}

.thumbnailButton {
  @extend .videoInner;
  background-color: transparent;
  border: 0;
  cursor: pointer;
  display: block;
  margin: 0;
  padding: 0;
  text-decoration: none;
}

.thumbnailImage {
  width: 100%;
}

.playIcon {
  height: 42px;
  left: calc(50% - 30px);
  position: absolute;
  top: calc(50% - 21px);
  transition: all 0.3s ease-in-out;
  width: 60px;
}

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

&lt;/div&gt;



&lt;p&gt;Now this is set up, a new component is needed to hold the video. YouTube does have an API that can be used to handle autoplaying: &lt;a href="https://developers.google.com/youtube/iframe_api_reference"&gt;https://developers.google.com/youtube/iframe_api_reference&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
This is not used within this example as it seemed overkill. Instead lets use a package that wraps this API in a React component called &lt;a href="https://www.npmjs.com/package/react-youtube"&gt;react-youtube&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import YouTube from "react-youtube";
import { videoInner } from "./style.module.scss";

const Player = ({ setHasLoaded, videoId }) =&amp;gt; {
  // Once the YouTube package (react-youtube) has loaded
  // tell the thumbnail it is no longer needed.
  // Play the video.
  const _onReady = (event) =&amp;gt; {
    setHasLoaded(true);
    event.target.playVideo();
  };

  return (
    &amp;lt;YouTube
      videoId={videoId}
      onReady={_onReady}
      className={videoInner}
      iframeClassName={videoInner}
    /&amp;gt;
  );
};

export default Player;

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

&lt;/div&gt;



&lt;p&gt;And that is it! See this CodeSandbox for the &lt;a href="https://codesandbox.io/s/load-youtube-video-in-react-on-click-of5olh?file=/src/App.js"&gt;full working example&lt;/a&gt;! Hopefully this helped you, and if you have any questions you can reach me at: &lt;a href="https://twitter.com/robertmars"&gt;@robertmars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>ux</category>
    </item>
    <item>
      <title>Lazy Loading Videos on Scroll with Vanilla JS and Intersection Observers</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Fri, 04 Aug 2023 11:59:04 +0000</pubDate>
      <link>https://forem.com/robmarshall/lazy-loading-videos-on-scroll-with-vanilla-js-and-intersection-observers-gk</link>
      <guid>https://forem.com/robmarshall/lazy-loading-videos-on-scroll-with-vanilla-js-and-intersection-observers-gk</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/lazy-loading-videos-on-scroll-with-vanilla-js-and-intersection-observers/"&gt;https://robertmarshall.dev/blog/lazy-loading-videos-on-scroll-with-vanilla-js-and-intersection-observers/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;As frontend developers, we often encounter the challenge of optimizing website performance. One powerful technique to achieve this is lazy loading.&lt;/p&gt;

&lt;p&gt;Lazy loading allows us to load assets, such as images or videos, only when they are needed, reducing initial page load times and conserving bandwidth.&lt;/p&gt;

&lt;p&gt;I have written pieces on using &lt;a href="https://robertmarshall.dev/blog/lazy-load-vimeo-video-iframe-show-on-scroll/"&gt;React and Lazy Loading&lt;/a&gt;, but sometimes Vanilla JS is needed.&lt;/p&gt;

&lt;p&gt;In this article, we’ll focus on lazy loading videos using the Intersection Observer API. By leveraging this built-in browser feature, we can efficiently detect when videos come into the user’s viewport and load them on demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Take a look at this CodeSandbox for a working example of &lt;a href="https://codesandbox.io/s/lazy-loading-videos-on-scroll-with-vanilla-js-and-intersection-observers-52xc2z"&gt;how to load a video on scroll&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Update&lt;/strong&gt; _&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I recently wrote a piece on deferring the video until a user clicks. This is a more effective solution than waiting until scroll. It is about YouTube but could be refactored to work with most providers.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://robertmarshall.dev/blog/on-click-lazy-load-video-iframe-in-react/"&gt;&lt;em&gt;robertmarshall.dev/blog/on-click-lazy-load-video-iframe-in-react/&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the HTML Structure
&lt;/h2&gt;

&lt;p&gt;Before we delve into the JavaScript code, let’s understand the HTML structure we’ll be working with.&lt;/p&gt;

&lt;p&gt;In the example, we’ll have multiple video elements, each representing a different video to be loaded on scroll.&lt;br&gt;
&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;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
    &lt;span class="na"&gt;data-video-url=&lt;/span&gt;&lt;span class="s"&gt;"https://player.vimeo.com/video/588259728?h=09704145bb&amp;amp;dnt=1&amp;amp;app_id=122963&amp;amp;autoplay=1&amp;amp;muted=1"&lt;/span&gt;
    &lt;span class="na"&gt;data-video-title=&lt;/span&gt;&lt;span class="s"&gt;"This is an iframe title"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"video-wrap"&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"
      overflow: hidden;
      padding-top: 56.25%;
      position: relative;
      width: 100%;
    "&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
    &lt;span class="na"&gt;data-video-url=&lt;/span&gt;&lt;span class="s"&gt;"https://player.vimeo.com/video/588259728?h=09704145bb&amp;amp;dnt=1&amp;amp;app_id=122963&amp;amp;autoplay=1&amp;amp;muted=1"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"video-wrap"&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"
      overflow: hidden;
      padding-top: 56.25%;
      position: relative;
      width: 100%;
    "&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;div&lt;/code&gt; element contains styling to ensure the video’s correct aspect ratio and prevent page jumps when the video finally loads. The first &lt;code&gt;div&lt;/code&gt; includes &lt;code&gt;data-video-url&lt;/code&gt; and &lt;code&gt;data-video-title&lt;/code&gt; attributes, which we will later grab to inject into the iframe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 2: The Intersection Observer API
&lt;/h2&gt;

&lt;p&gt;Now to cover the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API"&gt;Intersection Observer API&lt;/a&gt;. This is a built-in browser feature that allows us to observe changes in the intersection of elements with a viewport or a specific container.&lt;/p&gt;

&lt;p&gt;The Intersection Observer is perfect for our lazy loading implementation because it can detect when a video element comes into view, signalling the browser to load the video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 3: The JavaScript Implementation
&lt;/h2&gt;

&lt;p&gt;First, we need to wait for the DOM to be fully loaded before starting our JavaScript implementation. We’ll use the &lt;code&gt;DOMContentLoaded&lt;/code&gt; event to ensure our script runs at the right time.&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loading&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="nx"&gt;initVimeo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initVimeo&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;Next, let’s define the &lt;code&gt;initVimeo()&lt;/code&gt; function, which will be the starting point of the lazy loading implementation. It will use the querySelectorAll method to select all video elements with the &lt;code&gt;data-video-url&lt;/code&gt; attribute.&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;function&lt;/span&gt; &lt;span class="nx"&gt;initVimeo&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;videoWraps&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="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.video-wrap[data-video-url]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// More code will be added here later&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 4: Implementing Lazy Loading
&lt;/h2&gt;

&lt;p&gt;Inside the &lt;code&gt;initVimeo()&lt;/code&gt; function the Intersection Observer will check when each video element comes into view. If it’s the first time the element is visible (not yet loaded), it will to load the video dynamically.&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;function&lt;/span&gt; &lt;span class="nx"&gt;initVimeo&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;videoWraps&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="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.video-wrap[data-video-url]&lt;/span&gt;&lt;span class="dl"&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;videoStateMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&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;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entries&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;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isIntersecting&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;isIntersecting&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;videoStateMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&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;videoUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-video-url&lt;/span&gt;&lt;span class="dl"&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;videoTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-video-title&lt;/span&gt;&lt;span class="dl"&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;iframe&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="nx"&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;iframe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frameBorder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;autoplay; fullscreen; picture-in-picture&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowfullscreen&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;videoTitle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoTitle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;position: absolute; inset: 0; width: 100%; height: 100%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;videoStateMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&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="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;videoWraps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;videoWrap&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;videoStateMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoWrap&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="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoWrap&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;By leveraging the Intersection Observer API, we’ve successfully implemented lazy loading for videos, ensuring they load only when they are about to be displayed on the user’s screen. This optimization results in faster page load times and a smoother user experience.&lt;/p&gt;

&lt;p&gt;To see a working version of this code, &lt;a href="https://codesandbox.io/s/lazy-loading-videos-on-scroll-with-vanilla-js-and-intersection-observers-52xc2z"&gt;take a look at the Code Sandbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find me on &lt;a href="https://twitter.com/robertmars"&gt;Twitter&lt;/a&gt; if you have any questions.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>Using Gravity Forms with Headless WordPress and Next JS</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Thu, 03 Aug 2023 10:02:50 +0000</pubDate>
      <link>https://forem.com/robmarshall/using-gravity-forms-with-headless-wordpress-and-next-js-aik</link>
      <guid>https://forem.com/robmarshall/using-gravity-forms-with-headless-wordpress-and-next-js-aik</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/using-gravity-forms-with-headless-wordpress-and-next-js/"&gt;https://robertmarshall.dev/blog/using-gravity-forms-with-headless-wordpress-and-next-js/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;This article covers Next JS 13 with the app directory. That said, it could easily be ported to the pages structure.&lt;/p&gt;

&lt;p&gt;It is possible to set up Gravity Forms yourself. Either creating the queries and mutations needed using WpGraphql. Passing data back to Gravity Forms using either native fetch or Apollo. If you aren’t a fan of GraphQL, you might want to use the built in GF Rest API.&lt;/p&gt;

&lt;p&gt;However, why reinvent the wheel.&lt;/p&gt;

&lt;p&gt;This article aims to introduce a far cleaner, simpler way of using Gravity Forms with Next JS. It will provide a step by step walkthrough of what is needed, as well as provide an example git repository so you can copy/paste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get WordPress ready

&lt;ul&gt;
&lt;li&gt;Install Gravity Forms&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://github.com/wp-graphql/wp-graphql"&gt;wp-graphql&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://github.com/AxeWP/wp-graphql-gravity-forms"&gt;wp-graphql-gravity-forms&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Add next-gravity-forms to your Next JS project.&lt;/li&gt;
&lt;li&gt;Example repo with solution: &lt;a href="https://github.com/robmarshall/next-gravity-forms-example"&gt;&lt;/a&gt;&lt;a href="https://github.com/robmarshall/next-gravity-forms-example"&gt;https://github.com/robmarshall/next-gravity-forms-example&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
What Packages Do I Need?

&lt;ul&gt;
&lt;li&gt;WordPress&lt;/li&gt;
&lt;li&gt;Next JS&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Step 1 – Setup WordPress&lt;/li&gt;
&lt;li&gt;
Step 2 – Add Form Packages to Next JS

&lt;ul&gt;
&lt;li&gt;Add Package&lt;/li&gt;
&lt;li&gt;Environment Variables&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Step 3 – Using the Package Within Your Project

&lt;ul&gt;
&lt;li&gt;Getting Gravity Form Data from WordPress to Next JS&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Add Google reCaptcha to the Form&lt;/li&gt;
&lt;li&gt;Styling the Next Gravity Forms Package&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Packages Do I Need?
&lt;/h2&gt;

&lt;p&gt;We will use the following packages:&lt;/p&gt;

&lt;h3&gt;
  
  
  WordPress
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Gravity Forms&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wp-graphql/wp-graphql"&gt;wp-graphql&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AxeWP/wp-graphql-gravity-forms"&gt;wp-graphql-gravity-forms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Next JS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/next-gravity-forms"&gt;next-gravity-forms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 – Setup WordPress
&lt;/h2&gt;

&lt;p&gt;The first step is to get the WordPress site set up. This is pretty painless (much like the whole process TBH).&lt;/p&gt;

&lt;p&gt;The below exposes your Gravity Forms forms using GraphQL, so the frontend (Next) can get the form shape, and pass back user submissions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install and Activate Gravity Forms (get a license here if you need one)&lt;/li&gt;
&lt;li&gt;Install &amp;amp; activate WPGraphQL on your WordPress site – This can be installed from the plugin directory on your WordPress backend.&lt;/li&gt;
&lt;li&gt;Download zip from the wp-graphql-gravity-forms repository (&lt;a href="https://github.com/harness-software/wp-graphql-gravity-forms/releases"&gt;download latest release as a zip from here&lt;/a&gt;) and upload it to your WordPress install. Then activate the plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2 – Add Form Packages to Next JS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add Package
&lt;/h3&gt;

&lt;p&gt;Now add Next Gravity Forms to your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Use Yarn
yarn add next-gravity-forms

# Or NPM
npm i next-gravity-forms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment Variable
&lt;/h3&gt;

&lt;p&gt;Now add the environment variable needed. Next-gravity-forms grabs this and uses it to send back user submissions.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;NEXT_PUBLIC_WORDPRESS_API_URL=YOUR_ENDPOINT&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your WordPress GraphQL data is passed through a cache like &lt;a href="https://stellate.co/"&gt;Stellate&lt;/a&gt; (if its not, I recommend them!), set &lt;code&gt;NEXT_PUBLIC_WORDPRESS_API_URL&lt;/code&gt; with the cache URL, and add a secondary environment variable – &lt;code&gt;NEXT_PUBLIC_WORDPRESS_FORM_SUBMIT_URL&lt;/code&gt; – to handle submissions.&lt;/p&gt;

&lt;p&gt;This means you can still take advantage of any caching you may have set up, and bypass this when wanting to submit data. E.G.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_WORDPRESS_API_URL=YOUR_CACHED_ENDPOINT
NEXT_PUBLIC_WORDPRESS_FORM_SUBMIT_URL=YOUR_DIRECT_ENDPOINT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 – Using the Package Within Your Project
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create Frontend Form Component
&lt;/h3&gt;

&lt;p&gt;Next create a component for the client side code to live in.&lt;/p&gt;

&lt;p&gt;This is because the form component itself contains React Hook Forms, a client side library. It needs wrapping in:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;use client&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The component will look something like this:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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;GravityFormForm&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;next-gravity-forms&lt;/span&gt;&lt;span class="dl"&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;GravityForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="p"&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="nx"&gt;GravityFormForm&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;GravityForm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example in repository: &lt;a href="https://github.com/robmarshall/next-gravity-forms-example/blob/main/components/GravityForm.js"&gt;https://github.com/robmarshall/next-gravity-forms-example/blob/main/components/GravityForm.js&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Gravity Forms Data from WordPress into Next JS
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Getting a Single Form
&lt;/h4&gt;

&lt;p&gt;If you know the ID of the specific form you want to render, the setup is fairly simple.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getGravityForm&lt;/span&gt; &lt;span class="p"&gt;}&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;next-gravity-forms/server&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;GravityForm&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;components/GravityForm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Page&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;form&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;getGravityForm&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="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GravityForm&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;getGravityForm&lt;/code&gt; is a server side function, it &lt;em&gt;does not run&lt;/em&gt; on the client side. Make sure to &lt;em&gt;only&lt;/em&gt; use it within a Server Component or within an API route.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rendering Forms Dynamically
&lt;/h4&gt;

&lt;p&gt;This is a little more complicated (but not massively). The complexity is introduced by the fact that we want to dynamically pick a form to render on the client side. But can only retrieve the specific form data on the server side.&lt;/p&gt;

&lt;p&gt;This is solved by getting all forms (or a subset of them, if you only want to pick from X number of forms) and passing this collecting of data to the frontend.&lt;/p&gt;

&lt;p&gt;Once the frontend has the form data, the specific forms data can be passed to the &lt;code&gt;GravityFormForm&lt;/code&gt; component when needed.&lt;/p&gt;

&lt;p&gt;Take a look at the &lt;a href="https://github.com/robmarshall/next-gravity-forms-example/tree/main/app/dynamic"&gt;dynamic example within the repository&lt;/a&gt; to see how this works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Google reCaptcha to the Form
&lt;/h2&gt;

&lt;p&gt;The next-gravity-forms package is built so that reCaptcha “just works”.&lt;/p&gt;

&lt;p&gt;The only thing to check is that the reCaptcha details have been added to the Gravity Forms settings on the WordPress side.&lt;/p&gt;

&lt;p&gt;Take a look at the Gravity Forms help article for more info: &lt;a href="https://www.gravityforms.com/blog/add-recaptcha-to-your-forms/"&gt;https://www.gravityforms.com/blog/add-recaptcha-to-your-forms/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling the Next Gravity Forms Package
&lt;/h2&gt;

&lt;p&gt;There is no default styling currently packaged in with the next-gravity-forms package. This means that you will need to handle all the styling yourself.&lt;/p&gt;

&lt;p&gt;The GravityFormForm component uses exactly the same CSS classes as the PHP Gravity Forms HTML. This means you can move your styling from a pre-existing WordPress site to Gatsby very easily. It doesn’t include support for CSS-in-JS/styled components yet.&lt;/p&gt;

&lt;p&gt;There is baseline styling CSS file included in the example repo. This should get you to a point where you can see the form working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/robmarshall/next-gravity-forms-example/blob/main/app/form.css"&gt;Example form styling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example repo with solution if you missed it in the article:  &lt;a href="https://github.com/robmarshall/next-gravity-forms-example"&gt;https://github.com/robmarshall/next-gravity-forms-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hopefully this article has helped, and if you have any questions you can reach me at: &lt;a href="https://twitter.com/robertmars"&gt;@robertmars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>How To Permanently Redirect (301, 308) with Next JS</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Wed, 02 Aug 2023 10:24:04 +0000</pubDate>
      <link>https://forem.com/robmarshall/how-to-permanently-redirect-301-308-with-next-js-pfp</link>
      <guid>https://forem.com/robmarshall/how-to-permanently-redirect-301-308-with-next-js-pfp</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/how-to-permanently-redirect-301-308-with-next-js/" rel="noopener noreferrer"&gt;https://robertmarshall.dev/blog/how-to-permanently-redirect-301-308-with-next-js/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I recently worked on migrating a large content heavy site from pure WordPress to a headless WordPress and Next JS set up. During this change it was decided that a base URL for a blog section needed changing.&lt;/p&gt;

&lt;p&gt;The URL structure was to change from:&lt;/p&gt;

&lt;p&gt;“domain.com/ &lt;strong&gt;articles&lt;/strong&gt; /content-piece”&lt;/p&gt;

&lt;p&gt;to&lt;/p&gt;

&lt;p&gt;“domain.com/ &lt;strong&gt;blog&lt;/strong&gt; /content-piece”.&lt;/p&gt;

&lt;p&gt;For 500+ pieces of content.&lt;/p&gt;

&lt;p&gt;To make sure that all the old link juice was not lost, each one of these old links needed to be 301 redirected to the new URL structure.&lt;/p&gt;

&lt;p&gt;On previous sites I have used &lt;code&gt;.htaccess&lt;/code&gt; (oh, PHP) or handled redirects on the host level (Cloudflare pages make this super easy). But this time I wanted to keep everything internal, as Next JS has the tools to handle it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a 301 Redirect? Why is it Important?
&lt;/h2&gt;

&lt;p&gt;A 301 redirect is a code sent by the web server to the browser, telling it that the URL in question has been permanently moved to another URL. Google will also use this to re-write all of their indexing to the new pages, and pass the ranking power (or link juice) across to the new page.&lt;/p&gt;

&lt;p&gt;A 301 redirect should be used when a page (or pages) has been moved or removed from a website.&lt;/p&gt;

&lt;p&gt;An easy way to think about a 301 redirect is to think about when you move house. If you do not tell the postal service what your new address is and set up a re-direct, you will not get post that is set to your old address. Telling the postal service about your new address is the same as a 301.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hang on, Next JS Uses a 308 not 301
&lt;/h2&gt;

&lt;p&gt;This initially concerned me a little. Why would Next JS use a different status code?&lt;/p&gt;

&lt;p&gt;Well this is due to how browsers moved the goal posts. As the &lt;a href="https://nextjs.org/docs/api-reference/next.config.js/redirects" rel="noopener noreferrer"&gt;Next JS docs say&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Traditionally a 302 was used for a temporary redirect, and a 301 for a permanent redirect, but many browsers changed the request method of the redirect to GET, regardless of the original method. For example, if the browser made a request to POST /v1/users which returned status code 302 with location /v2/users, the subsequent request might be GET /v2/users instead of the expected POST /v2/users. Next.js uses the 307 temporary redirect, and 308 permanent redirect status codes to explicitly preserve the request method used.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In essence, a 308 is more reliable.&lt;/p&gt;

&lt;p&gt;So does Google treat a 308 the same as a 301? Is this going to cause issues for search indexing? No, this will not cause any issues. Google’s Search Advocate John Mueller, has said that 308 redirects are treated in the same way as 301 by Google crawlers. Here is &lt;a href="https://twitter.com/JohnMu/status/994633389356429312?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E994633389356429312%7Ctwgr%5Eeea1b79fde5c65d1c4ee35b6434ce6d96f7bdde7%7Ctwcon%5Es1_&amp;amp;ref_url=https%3A%2F%2Fwww.infidigit.com%2Fnews%2F308-redirects-for-seo-are-they-better-than-301%2F" rel="noopener noreferrer"&gt;his tweet&lt;/a&gt; about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Set up a Permanent Redirect in Next JS
&lt;/h2&gt;

&lt;p&gt;Next JS is essential a React App bundled up with a number of other third party packages, and other really helpful internal functions. Some of these packages and functions help with the speediness of the site. Others handle how a user is able to navigate around the site with different URLs.&lt;/p&gt;

&lt;p&gt;A lot of Next.js magic comes from the &lt;code&gt;next.config.js&lt;/code&gt; file. Here you can set up any number of options, but the one we care about it the &lt;code&gt;redirects&lt;/code&gt; function.&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// All the other config options you may have...&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;redirects&lt;/span&gt;&lt;span class="p"&gt;()&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="c1"&gt;// What the user typed in the browser&lt;/span&gt;
                &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/articles/:path*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;// Where the user will be redirected to&lt;/span&gt;
                &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/blog/:path*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;// If the destination is a permanent redirect (308)&lt;/span&gt;
                &lt;span class="na"&gt;permanent&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="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 above object shows setting the &lt;code&gt;source&lt;/code&gt; value to “article” with a catch all of any URL after the initial string. It then sets the &lt;code&gt;destination&lt;/code&gt; using the same logic.&lt;/p&gt;

&lt;p&gt;Using the &lt;code&gt;:path*&lt;/code&gt; is known within Next JS as &lt;a href="https://nextjs.org/docs/api-reference/next.config.js/rewrites#path-matching" rel="noopener noreferrer"&gt;Wildcard Path Matching&lt;/a&gt;. For example &lt;code&gt;/blog/:slug*&lt;/code&gt; will match &lt;code&gt;/blog/a/b/c/d/content-piece&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally &lt;code&gt;permanent&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;. This tells Next JS that the redirection should be sent with a status code of 308. If this is set to false then it sends a 307 (traditionally this would have been 302, but Next JS changed this due to the reasoning outlined above).&lt;/p&gt;

&lt;p&gt;Hopefully this helped you, and if you have any questions you can reach me at: &lt;a href="https://twitter.com/robertmars" rel="noopener noreferrer"&gt;@robertmars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>How to Fix Issues With CSS Position Sticky Not Working</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Sat, 26 Nov 2022 11:54:03 +0000</pubDate>
      <link>https://forem.com/robmarshall/how-to-fix-issues-with-css-position-sticky-not-working-4a18</link>
      <guid>https://forem.com/robmarshall/how-to-fix-issues-with-css-position-sticky-not-working-4a18</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/solution-to-why-css-position-sticky-is-not-working/"&gt;https://robertmarshall.dev/blog/solution-to-why-css-position-sticky-is-not-working/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;An issue that took far longer than it should have to solve.&lt;/p&gt;

&lt;p&gt;I was using the CSS &lt;code&gt;position: sticky&lt;/code&gt; on my header so it moved down as the user scrolled. However when I added the mobile menu which transitioned in from the right, the sticky header broke. Could not understand why!&lt;/p&gt;

&lt;p&gt;Turns out &lt;code&gt;sticky&lt;/code&gt; does not play nicely with most &lt;code&gt;overflow&lt;/code&gt; values.&lt;/p&gt;

&lt;p&gt;If you are trying to use &lt;code&gt;position: sticky&lt;/code&gt; and it is not working, it is because one of the elements wrapping it is using &lt;code&gt;overflow&lt;/code&gt; with a value of &lt;code&gt;hidden&lt;/code&gt;, &lt;code&gt;auto&lt;/code&gt; or &lt;code&gt;scroll&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to solve the sticky problem?
&lt;/h2&gt;

&lt;p&gt;There is a new (ish) value that can be used with &lt;code&gt;overflow&lt;/code&gt;. It is &lt;code&gt;clip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As Mozilla puts it in their &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/overflow"&gt;documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Similar to hidden, the content is clipped to the element’s padding box. The difference between clip and hidden is that the clip keyword also forbids all scrolling, including programmatic scrolling. The box is not a scroll container, and does not start a new formatting context. If you wish to start a new formatting context, you can use display: flow-root to do so.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Is Clip Supported on all modern browsers.
&lt;/h2&gt;

&lt;p&gt;At time of writing (23/11/2022) all current versions of modern browsers support this functionality. Take a look at&lt;a href="https://caniuse.com/mdn-css_properties_overflow_clip"&gt;Can I Use&lt;/a&gt; if you want to check for yourself.&lt;/p&gt;

</description>
      <category>css</category>
    </item>
    <item>
      <title>Use Yoast Sitemap with Next JS and Headless WordPress</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Mon, 07 Nov 2022 13:17:12 +0000</pubDate>
      <link>https://forem.com/robmarshall/use-yoast-sitemap-with-next-js-and-headless-wordpress-593j</link>
      <guid>https://forem.com/robmarshall/use-yoast-sitemap-with-next-js-and-headless-wordpress-593j</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/use-yoast-sitemap-with-next-js-and-headless-wordpress/"&gt;https://robertmarshall.dev/blog/use-yoast-sitemap-with-next-js-and-headless-wordpress/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;There are many perfectly valid ways of creating sitemaps for Next JS. Especially when using WordPress as a headless CMS.&lt;/p&gt;

&lt;p&gt;You could generate the XML yourself by &lt;a href="https://www.ohmycrawl.com/next-js-sitemap/"&gt;grabbing all the URLs&lt;/a&gt; yourself with custom code. You could use a plugin like &lt;a href="https://www.npmjs.com/package/next-sitemap"&gt;next-sitemap&lt;/a&gt; to do the hard work for you.&lt;/p&gt;

&lt;p&gt;These are both good options. But only if you are not wanting to use ISR, and are happy to rebuild the whole site on each content change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Create a lightweight proxy that grabs the Yoast sitemap data, point them to your Next JS domain, and rewrite any internal URLS to that same domain. Solution here.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
The Problem (brief)

&lt;ul&gt;
&lt;li&gt;Rebuild the whole site with SSG&lt;/li&gt;
&lt;li&gt;Run a pre-build script&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
The Solution

&lt;ul&gt;
&lt;li&gt;How to use Yoast Sitemap with Next JS&lt;/li&gt;
&lt;li&gt;Creating a Proxy&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problem (brief)
&lt;/h2&gt;

&lt;p&gt;Myself and the clients I work with tend to build sites that are very content heavy, and with lots of images. As well as this, content gets published regularly. We also need the sitemap to be updated after each publish, edit or deletion of content.&lt;br&gt;&lt;br&gt;
This can be managed in a number of ways. We could:&lt;/p&gt;
&lt;h3&gt;
  
  
  Rebuild the whole site with SSG
&lt;/h3&gt;

&lt;p&gt;Rebuild the whole site using SSG every time content changes. This would generate a clean and up-to-date site map.&lt;br&gt;&lt;br&gt;
Unfortunately this isn’t a great solution. The build times will get longer as the content grows. We would see builds failing as Vercel/Netlify (your build tool of choice) timed out. It is also overkill to re-build a whole site for one piece of content. And not at all efficient.&lt;/p&gt;
&lt;h3&gt;
  
  
  Run a pre-build script
&lt;/h3&gt;

&lt;p&gt;This idea initially appealed to me. It shouldn’t be too much of a nightmare. Should be as simple as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run a query before the whole ISR build kicks off.&lt;/li&gt;
&lt;li&gt;Grab all the site data with a GraphlQl query.&lt;/li&gt;
&lt;li&gt;Build out a new XML file.&lt;/li&gt;
&lt;li&gt;Push to the host.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My concern with this is that when hosting Next JS with Vercel/Netlify etc. we may not have created a directory yet. Especially if this is the first time the site has been built. We need to know where the file is to be sent. Their may be permissions issues. A rabbit hole I CBA with.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Use the Yoast sitemap files.&lt;/p&gt;

&lt;p&gt;Every headless WordPress site I build generally has Yoast installed. It makes handling a pages SEO far simpler, and content teams have usually worked with it before.&lt;/p&gt;

&lt;p&gt;Yoast also generates its own sitemap XML. This is created as ‘sitemap_index.xml’ and then WordPress redirects it to ‘sitemap.xml’. So why not use this?&lt;/p&gt;

&lt;p&gt;This solution was inspired by how the &lt;a href="https://www.patronage.org/"&gt;Patronage&lt;/a&gt; &lt;a href="https://github.com/patronage/bubs-next"&gt;bubs-next starter&lt;/a&gt; handles their sitemaps.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a Sitemap XML Template
&lt;/h3&gt;

&lt;p&gt;The first step is to create an XML template that our Yoast sitemap content can be injected into.&lt;/p&gt;

&lt;p&gt;Download the XML file from this GitHub gist and add it to your &lt;code&gt;/public&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;XML Template: &lt;a href="https://gist.github.com/robmarshall/caafd8a4328db0e58cf721a307af64e1"&gt;https://gist.github.com/robmarshall/caafd8a4328db0e58cf721a307af64e1&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating a Proxy
&lt;/h3&gt;

&lt;p&gt;The next step in using the Yoast sitemap from a headless WordPress install inside of Next JS is to create a proxy.&lt;/p&gt;

&lt;p&gt;The purpose of this is to get the sitemap file contents from WordPress and serve it on your domain.&lt;/p&gt;
&lt;h4&gt;
  
  
  Create the API file
&lt;/h4&gt;

&lt;p&gt;Create a file named &lt;code&gt;sitemap-proxy.js&lt;/code&gt; within &lt;code&gt;/pages/api/&lt;/code&gt;. This creates a server side function that we can use via ‘/api/sitemap-proxy’.&lt;/p&gt;
&lt;h4&gt;
  
  
  Install a Package
&lt;/h4&gt;

&lt;p&gt;For the proxy to work, you will need to install a package.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/isomorphic-unfetch"&gt;isomorphic-unfetch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a helpful wrapper for fetch that works on both client/server side.&lt;/p&gt;

&lt;p&gt;To install: &lt;code&gt;npm i isomorphic-unfetch&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Add the proxy code
&lt;/h4&gt;

&lt;p&gt;Add the below code to your &lt;code&gt;sitemap-proxy.js&lt;/code&gt; file.&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isomorphic-unfetch&lt;/span&gt;&lt;span class="dl"&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;WORDPRESS_URL&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;NEXT_PUBLIC_WORDPRESS_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g. 'https://my-wordpressdomain.com'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FRONTEND_URL&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;NEXT_PUBLIC_FRONTEND_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// e.g. 'https://localhost:3000'&lt;/span&gt;

&lt;span class="c1"&gt;// Global regex search allows replacing all URLs&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HOSTNAME_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WORDPRESS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&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;string&lt;/span&gt; &lt;span class="o"&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;args&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&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;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&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;content&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;contentType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the page that was requested. The manual option allows us to process redirects&lt;/span&gt;
    &lt;span class="c1"&gt;// manually (if we get a redirect). So the next step of this function can work.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upstreamRes&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;fetch&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;WORDPRESS_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;req&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="s2"&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;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Check for redirects.&lt;/span&gt;
    &lt;span class="c1"&gt;// This allows for any internal WordPress redirect. For example the /sitemap_index.xml to /sitemap.xml.&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;upstreamRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;upstreamRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;310&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;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;upstreamRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;location&lt;/span&gt;&lt;span class="dl"&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;locationURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&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;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upstreamRes&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="c1"&gt;// Follow once only if on a wordpress domain.&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;locationURL&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="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WORDPRESS_URL&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;locationURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&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;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upstreamRes&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response2&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locationURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manual&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="nx"&gt;content&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;response2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// If there were more than two redirects, throw an error.&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;`abort proxy to non wordpress target &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;locationURL&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="s2"&gt; to avoid redirect loops`&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// There are no redirects, get original response text.&lt;/span&gt;
        &lt;span class="nx"&gt;content&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;upstreamRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;upstreamRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&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="c1"&gt;// If the current URL includes 'sitemap'.&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;req&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;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sitemap&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="c1"&gt;// Find any URLS inside the XML content that include "sitemap",&lt;/span&gt;
        &lt;span class="c1"&gt;// and replace the WordPress URL with the current site URL.&lt;/span&gt;
        &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;replace&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="nx"&gt;HOSTNAME_REGEX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FRONTEND_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Change sitemap xsl file path to local&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sitemapFind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//(.*)sitemap-template.xsl&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;sitemapReplace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sitemap-template.xsl&lt;/span&gt;&lt;span class="dl"&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;SITEMAP_XSL_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sitemapFind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;replace&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="nx"&gt;SITEMAP_XSL_REGEX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sitemapReplace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&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="s1"&gt;max-age=60&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Add the Rewrites
&lt;/h4&gt;

&lt;p&gt;The final step is to make sure that Next JS knows which URLs to pass through the proxy. For this it needs a &lt;code&gt;rewrites&lt;/code&gt; function adding to the &lt;code&gt;next.config.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This will look like:&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="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;rewrites&lt;/span&gt;&lt;span class="p"&gt;()&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="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/(.*)sitemap.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/sitemap-proxy&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;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/sitemap(.*).xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/sitemap-proxy&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function should be added within &lt;code&gt;module.exports&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What this does is target any URL with &lt;code&gt;sitemap&lt;/code&gt; in the name, and passes it through the proxy.&lt;/p&gt;

&lt;p&gt;Now if you spin your site up and navigate to ‘/sitemap.xml’ you should be shown the new, proxied sitemap from Yoast.&lt;/p&gt;

&lt;p&gt;Hopefully this helped you, and if you have any questions you can reach me at: &lt;a href="https://twitter.com/robertmars"&gt;@robertmars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>How to Loop over Jest Unit Tests</title>
      <dc:creator>Robert Marshall</dc:creator>
      <pubDate>Mon, 26 Sep 2022 21:33:43 +0000</pubDate>
      <link>https://forem.com/robmarshall/how-to-loop-in-jest-unit-tests-3hb2</link>
      <guid>https://forem.com/robmarshall/how-to-loop-in-jest-unit-tests-3hb2</guid>
      <description>

&lt;p&gt;This article was originally posted (and is more up to date) at &lt;a href="https://robertmarshall.dev/blog/how-to-loop-in-jest-unit-tests/"&gt;https://robertmarshall.dev/blog/how-to-loop-in-jest-unit-tests/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Sometimes when writing Jest unit tests you are going to want to be able to loop over items to quickly test different cases. Whether this be looping over an array of items, or counting between numbers in your Jest Test. This will massively simplify your Jest test suite.&lt;/p&gt;

&lt;p&gt;There are a number of ways to do this. This post aims to outline the ways that I have looped through Jest tests in the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looping over tests with Jest.Each
&lt;/h2&gt;

&lt;p&gt;For very basic repetitive Jest tests I would use &lt;code&gt;jest.each&lt;/code&gt;. It allows you to keep duplicating the same test with different data. You can write the test once and let the data do the work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Basic Arrays and printf
&lt;/h3&gt;

&lt;p&gt;An example of this is:&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="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;])(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.add(%i, %i)&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="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&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 above code is taking in a set of data within an array, The name of the test using printf to inject parameters into the text, and the arguments to be passed into the test itself.&lt;/p&gt;

&lt;p&gt;For more information about &lt;code&gt;jest.each&lt;/code&gt; take a look at the &lt;a href="https://jestjs.io/docs/api#testeachtablename-fn-timeout"&gt;Jest documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Array of Objects
&lt;/h3&gt;

&lt;p&gt;Rather than using printf, you can pass in an array of objects and reference them using string literals. Like so:&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="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;a&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="na"&gt;b&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="na"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;a&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="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;b&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="na"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;])(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.add($a, $b)&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="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&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;I personally prefer this. It is more explicit in what each item is, and allows you to build a more complex test data set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Jest.Each with a Table
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;jest.each&lt;/code&gt; API also lets you pass data into it in a more ‘visual’ way. Using one large string literal you can shape the data in a table.&lt;/p&gt;

&lt;p&gt;This looks like:&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="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="s2"&gt;`
  a | b | expected
  &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="s2"&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="s2"&gt; | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="mi"&gt;2&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="mi"&gt;1&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="mi"&gt;2&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="mi"&gt;3&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="mi"&gt;2&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="mi"&gt;1&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="mi"&gt;3&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns $expected when $a is added $b&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="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&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 first row sets up the table headings, and the rest of the rows using template literals to add the values. Then the test title itself using template literals to inject the data into the string.&lt;/p&gt;

&lt;p&gt;Not my top choice, but some people like this way of handling the tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a For Loop in Jest Tests
&lt;/h2&gt;

&lt;p&gt;The for loop is my go to when wanting to dynamically loop through Jest tests. In this way of writing loops in jest there is no need for the printf variable injection. Call me old fashioned (or wrong), but I also like the flexibility of being able to fully control the tests, and how they are created.&lt;/p&gt;

&lt;p&gt;For example, the &lt;code&gt;for&lt;/code&gt; loop approach allows me to loop &lt;code&gt;describe&lt;/code&gt; jest test blocks, as well as the unit tests inside. However with this freedom comes the need to restrict yourself. Otherwise the tests could become to over-engineered and end up being too confusing.&lt;/p&gt;

&lt;p&gt;Simple tests are better.&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;testData&lt;/span&gt; &lt;span class="o"&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;a&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="na"&gt;b&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="na"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;a&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="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;b&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="na"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`.add(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;a&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;testData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;b&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;expected&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 above shows how the for loop would be used to create dynamically generated Jest tests. However it should be used sparingly, and the &lt;code&gt;jest.each&lt;/code&gt; will be able to handle most situations. The &lt;code&gt;for&lt;/code&gt; loop is really more for complex situations.&lt;/p&gt;

&lt;p&gt;For more Jest hints and tips take a look at the &lt;a href="https://robertmarshall.dev/blog/category/jest/"&gt;Jest Testing Category&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Hopefully this helped you, and if you have any questions you can reach me at: &lt;a href="https://twitter.com/robertmars"&gt;@robertmars&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jest</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
