<?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: Zach Koch</title>
    <description>The latest articles on Forem by Zach Koch (@danshari).</description>
    <link>https://forem.com/danshari</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%2F1756731%2Fb3a4ccca-5b05-4d53-939f-56ea4ebcfe9f.png</url>
      <title>Forem: Zach Koch</title>
      <link>https://forem.com/danshari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/danshari"/>
    <language>en</language>
    <item>
      <title>Running a Docker Swarm on Raspberry Pi 5</title>
      <dc:creator>Zach Koch</dc:creator>
      <pubDate>Mon, 09 Sep 2024 17:25:46 +0000</pubDate>
      <link>https://forem.com/danshari/running-a-docker-swarm-on-raspberry-pi-5-4dm3</link>
      <guid>https://forem.com/danshari/running-a-docker-swarm-on-raspberry-pi-5-4dm3</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;This article assumes Raspberry Pi 5s running Pi OS 64-bit connected to your local network.&lt;/p&gt;

&lt;p&gt;You can use direct input, ssh, or my new favorite &lt;a href="https://connect.raspberrypi.com/devices" rel="noopener noreferrer"&gt;Raspberry Pi Connect&lt;/a&gt; to run these commands&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you follow my install instructions below, you will likely need to add &lt;strong&gt;sudo&lt;/strong&gt; before each docker command. Otherwise you get an error like &lt;code&gt;permission denied while trying to connect to the Docker daemon socket at ...&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Docker swarm layout
&lt;/h2&gt;

&lt;p&gt;A Docker swarm consists of &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;manager nodes&lt;/strong&gt; which run tasks and add &lt;strong&gt;availability&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;worker nodes&lt;/strong&gt; which host containers and add &lt;strong&gt;capacity&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The recommended minimum of manager nodes is &lt;strong&gt;3&lt;/strong&gt; for high availability. This is so you can take one down and the other two will continue to run the swarm.&lt;/p&gt;

&lt;p&gt;Manager nodes also act as workers with extra overhead.&lt;/p&gt;

&lt;p&gt;In my case I have 3 devices total so I will be making all of them managers, but any additional devices will be workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Docker
&lt;/h2&gt;

&lt;p&gt;You can follow my &lt;a href="https://dev.to/danshari/install-docker-engine-on-raspberry-pi-os-64-bit-8hl"&gt;guide&lt;/a&gt; to install docker on your raspberry pi 5.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize the swarm
&lt;/h2&gt;

&lt;p&gt;Use ifconfig to see your ip&lt;/p&gt;

&lt;p&gt;It should be the first ip listed likely under:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eth0&lt;/code&gt; for ethernet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wlan0&lt;/code&gt; for wifi&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then use the following command to initialize your first manager node&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker swarm init &lt;span class="nt"&gt;--advertise-addr&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;ip address]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add other managers
&lt;/h2&gt;

&lt;p&gt;On the node where you initialized your swarm run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker swarm join-token manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the output and for each of your other devices input this line to add them as manager nodes&lt;/p&gt;

&lt;h2&gt;
  
  
  Add worker nodes
&lt;/h2&gt;

&lt;p&gt;If you want to add worker nodes int he future, the process is the same but you use this line on a manager node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker swarm join-token worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test by adding a service
&lt;/h2&gt;

&lt;p&gt;To test your swarm use this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker service create &lt;span class="nt"&gt;--replicas&lt;/span&gt; 5 &lt;span class="nt"&gt;--name&lt;/span&gt; helloworld alpine ping docker.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a single service with 5 replicas/containers of helloworld running.&lt;/p&gt;

&lt;p&gt;To see the service run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker service &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to see where the service has placed the replicas use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker service ps helloworld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the nodes column you should see that it has spread the replicas across your devices&lt;/p&gt;

&lt;p&gt;To remove the service and containers enter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker service &lt;span class="nb"&gt;rm &lt;/span&gt;helloworld
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>raspberrypi</category>
      <category>docker</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Install Docker Engine on Raspberry Pi OS (64-bit)</title>
      <dc:creator>Zach Koch</dc:creator>
      <pubDate>Thu, 05 Sep 2024 17:21:30 +0000</pubDate>
      <link>https://forem.com/danshari/install-docker-engine-on-raspberry-pi-os-64-bit-8hl</link>
      <guid>https://forem.com/danshari/install-docker-engine-on-raspberry-pi-os-64-bit-8hl</guid>
      <description>&lt;p&gt;This was tested on a &lt;code&gt;Raspberry Pi 5&lt;/code&gt; with a fresh install of &lt;code&gt;Pi OS 64-bit bookworm&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These instructions are available on the official site, but they are somewhat hidden away.&lt;/p&gt;

&lt;p&gt;If you want to skip reading the instructions I have made a &lt;a href="https://github.com/ezekeal/scripts/blob/main/docker-pi.sh" rel="noopener noreferrer"&gt;script&lt;/a&gt; that you can run directly from the terminal of your Pi&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://raw.githubusercontent.com/ezekeal/scripts/main/docker-pi.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add GPG Keys
&lt;/h2&gt;

&lt;p&gt;you will get no output from these commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/debian/gpg &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.asc
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.asc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add the Docker repositories
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; stable"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this will also have no output&lt;/p&gt;

&lt;p&gt;now update&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a line with &lt;code&gt;https://download.docker.com/linux/debian&lt;/code&gt; if the previous steps were completed correctly&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the Docker packages
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test your installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Using reCAPTCHA v3 with Next.js 14</title>
      <dc:creator>Zach Koch</dc:creator>
      <pubDate>Wed, 04 Sep 2024 20:45:35 +0000</pubDate>
      <link>https://forem.com/danshari/using-recaptcha-v3-with-nextjs-14-5cc7</link>
      <guid>https://forem.com/danshari/using-recaptcha-v3-with-nextjs-14-5cc7</guid>
      <description>&lt;h2&gt;
  
  
  Using useFormState and useActionState
&lt;/h2&gt;

&lt;p&gt;Next.js documentation currently encourages the use of &lt;code&gt;useFormState&lt;/code&gt; and &lt;code&gt;useActionState&lt;/code&gt; to submit form data to your server actions.&lt;/p&gt;

&lt;p&gt;I will show you how I got Google's reCaptcha v3 working with &lt;code&gt;useFormState&lt;/code&gt;. This may also apply to &lt;code&gt;useActionState&lt;/code&gt;, but I have not tested it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up reCAPTCHA
&lt;/h2&gt;

&lt;p&gt;Go to the &lt;a href="https://www.google.com/recaptcha/about/" rel="noopener noreferrer"&gt;reCAPTCHA v3 admin console&lt;/a&gt; and create a new site if you don't have one yet&lt;/p&gt;

&lt;p&gt;Your configuration should have&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A label for your project&lt;/li&gt;
&lt;li&gt;Score Based (v3) selected&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;localhost&lt;/code&gt; and &lt;code&gt;your site url&lt;/code&gt; both added as domains&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;After submitting the next page will let you copy the keys that you will need to add to your project in the next section&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Setting up environment variables
&lt;/h2&gt;

&lt;p&gt;In your project create a file called &lt;code&gt;.env.local&lt;/code&gt; if you don't have one&lt;/p&gt;

&lt;p&gt;In this file add your keys&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.env.local&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 bash
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=yoursitekeygoeshere
RECAPTCHA_SECRET_KEY=yoursecretkeygoeshere


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

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Make sure you have this file added to &lt;code&gt;.gitignore&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;adding &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; to our variable allows it to be accessed in the browser&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  react-google-recaptcha-v3
&lt;/h2&gt;

&lt;p&gt;install &lt;a href="https://www.npmjs.com/package/react-google-recaptcha-v3" rel="noopener noreferrer"&gt;react-google-recaptcha-v3&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 bash
npm i -S react-google-recaptcha-v3


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

&lt;/div&gt;

&lt;p&gt;This package comes with &lt;code&gt;GoogleReCaptchaProvider&lt;/code&gt;, a component that runs the reCaptcha script that detects bots and provides the result to any child components.&lt;/p&gt;

&lt;p&gt;The documentation states that it should places as high as possible in the tree, but I found that I would get errors if it was outside the bounds of a client component.&lt;/p&gt;

&lt;p&gt;In my case I wrapped it in the same file as my form component&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Contact.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 ts
'use client';
import {
    GoogleReCaptchaProvider, useGoogleReCaptcha
} from 'react-google-recaptcha-v3';

export default function Contact() {
    return (
        &amp;lt;GoogleReCaptchaProvider
            reCaptchaKey={
                process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ?? ''
            }&amp;gt;
            &amp;lt;ContactForm /&amp;gt;
        &amp;lt;/GoogleReCaptchaProvider&amp;gt;
    )
}

function ContactForm() {
    // my form component
}


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Integrate with useFormState
&lt;/h2&gt;

&lt;p&gt;Normally we would set up &lt;code&gt;useFormState&lt;/code&gt; by providing it with a server action that handles our form data.&lt;/p&gt;

&lt;p&gt;In this case we want to add the reCAPTCHA data just before submitting to the server.&lt;/p&gt;

&lt;p&gt;To do this we will&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make a function with that accepts &lt;code&gt;prevState&lt;/code&gt; and &lt;code&gt;formData&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;get our captcha response from the reCAPTCHA script using executeRecaptcha&lt;/li&gt;
&lt;li&gt;attach the captcha response to our form as a form field&lt;/li&gt;
&lt;li&gt;send it off to our server action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Contact.tsx&lt;/code&gt;&lt;/strong&gt; just inside your form component&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 ts
const initialState: MessageState = { message: null, errors: {}};
const [state, formAction] = useFormState(addRecaptcha, initialState);
const { executeRecaptcha } = useGoogleReCaptcha();

async function addRecaptcha(prevState: MessageState, formData: FormData) {
    let gRecaptchaToken = ''
    if (executeRecaptcha) {
        gRecaptchaToken = await executeRecaptcha('contactMessage');
    }
    formData.set('captcha', gRecaptchaToken);

    return createMessage(prevState, formData)
}


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

&lt;/div&gt;

&lt;p&gt;The form action will go at the top of your form&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Contact.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formAction&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// your form&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;

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

&lt;/div&gt;

&lt;p&gt;When you submit, the token that is created by the captcha provider will be sent to your server action as just another field&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating captcha data
&lt;/h2&gt;

&lt;p&gt;In your server action you will recieve your captcha token as part of your form data.&lt;/p&gt;

&lt;p&gt;You can send the token to Google to verify&lt;/p&gt;

&lt;p&gt;If the verification fails you can treat it the same way you would treat validation for any failed field in your form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Contact.tsx&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateCaptcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;captchaToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;minimumCaptchaScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;secretKey&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;RECAPTCHA_SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;captchaToken&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;captchaResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.google.com/recaptcha/api/siteverify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&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;res&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;captchaResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`captcha score: &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;score&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;minimumCaptchaScore&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;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;validateCaptcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;captcha&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The captcha score I'm still playing with, there doesn't seem to be a clear answer, but in my testing I usually get a &lt;code&gt;0.9&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;a href="https://www.linkedin.com/in/sean-richardson-/" rel="noopener noreferrer"&gt;friend of mine&lt;/a&gt; pointed out that using &lt;code&gt;FormData&lt;/code&gt; would be more secure than building the url with a template string.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hopefully that is enough to help you integrate captcha. I'll update this for &lt;code&gt;useActionState&lt;/code&gt; in the future. I am currently using this on my own site. Let me know in the comments if you spot any issues or if anything is unclear.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>react</category>
      <category>security</category>
    </item>
  </channel>
</rss>
