<?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: Michael La Posta</title>
    <description>The latest articles on Forem by Michael La Posta (@mlaposta).</description>
    <link>https://forem.com/mlaposta</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%2F1240482%2Fec075d21-d3f1-49fe-95de-c475cd5c3b78.jpg</url>
      <title>Forem: Michael La Posta</title>
      <link>https://forem.com/mlaposta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mlaposta"/>
    <language>en</language>
    <item>
      <title>In a World with AI, Where Will the Future of Developers Lie?</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Thu, 25 Apr 2024 14:30:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/in-a-world-filled-with-ai-where-will-developers-lie-1515</link>
      <guid>https://forem.com/mlaposta/in-a-world-filled-with-ai-where-will-developers-lie-1515</guid>
      <description>&lt;p&gt;Ever since OpenAI released ChatGPT, AI has been at the forefront of many discussions, including people wondering about the future of work, among other things.&lt;/p&gt;

&lt;p&gt;People are worried that AI will take their jobs, and I personally believe that developers are no exception. A belief that a lot of other developers don't seem to share at the moment.&lt;/p&gt;

&lt;p&gt;Now of course "AI" isn't a new thing, as it exists in perhaps more basic forms in many of the tools we already use every day. But the kind of AI that's being developed now is a lot more advanced, and it's starting to encroach on areas that were previously thought to be untouchable by computers and/or machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rise of ChatGPT and Large Language Models
&lt;/h2&gt;

&lt;p&gt;When ChatGPT was first released, I'll admit I didn't pay much attention to it. I figured it was just another chatbot, and I didn't see how it could be much different from the ones I had used before in a professional setting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fy03v6hzbnwtuhhelvt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fy03v6hzbnwtuhhelvt.jpg" alt="AI coming out of Computer Screen" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But I kept seeing "LLMs" pop up in discussions online, and I started to see some of the things that people were doing with it. I saw people using it mostly to generate code and write content, but eventually other things as well. &lt;/p&gt;

&lt;p&gt;It was at that point that I realized I needed to start paying attention to ChatGPT and LLMs, and get a better understanding of what they were, and how I could use them to my advantage.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a Large Language Model?
&lt;/h3&gt;

&lt;p&gt;I won't bother trying to explain what an LLM is, because frankly, I'm not smart enough to fully understand its complexities! 🤪&lt;/p&gt;

&lt;p&gt;But my basic understanding is that essentially, it's a model (code) that's been trained on a massive amount of existing texts (and data), and is able to make predictions of the next most probable word in a sentence based on the words that have come before it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Geeks for Geeks has a &lt;a href="https://www.geeksforgeeks.org/large-language-model-llm/"&gt;much more in-depth article&lt;/a&gt; on the topic if you're interested in learning more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For things like computer code especially, where the rules are somewhat straight forward, it's easy to see how LLM-based AI can really shine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using ChatGPT to be more productive
&lt;/h3&gt;

&lt;p&gt;When I finally decided to look into what all the fuss was about a few years ago, LLMs had already been gaining quite a bit of momentum, and had increased in complexity quite a bit.&lt;/p&gt;

&lt;p&gt;So I tried it initially for some useless things like asking it how it was doing, or to tell me a joke.&lt;/p&gt;

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

&lt;p&gt;But I quickly moved to testing it out with code snippets, using GitHub Copilot in VS Code, and I was blown away by how well it could predict what I was trying to write.&lt;/p&gt;

&lt;p&gt;I mean it was entirely wrong a lot of the time... probably like 50% or more of the time. And often it would get into these endless loops where it would repeat the same 5 to 10 lines or so of code over and over again.&lt;/p&gt;

&lt;p&gt;But when it got the code right, holy crap was it ever right! A bit eerie really, as it was able to predict what I was trying to write before I even finished typing it out. It was like having a pair programmer that was able to read my mind.&lt;/p&gt;

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

&lt;p&gt;But was it really making me more productive? I mean, I was able to write code faster, but I was also spending a lot of time correcting it. And I was also spending a lot of time trying to coax it to suggest the code I wanted.&lt;/p&gt;

&lt;p&gt;But I digress...&lt;/p&gt;

&lt;h2&gt;
  
  
  Are Developers Doomed?
&lt;/h2&gt;

&lt;p&gt;Ok, so back to the topic at hand: &lt;strong&gt;the future of AI and developers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whenever I see this topic come up online, it's quickly shut down by a lot of devs who say that AI will never be able to replace them because there's just too much creativity and problem solving involved in what they do.&lt;/p&gt;

&lt;p&gt;Developing an application isn't just about hacking code after all.&lt;/p&gt;

&lt;p&gt;It's about understanding the initial problem or design request. It's about getting all of the details, and understanding the requirements. It's about architecting the environment and figuring out what framework, DB, and APIs will be needed, how to structure the code, and how to make it maintainable and scalable. It's about testing and debugging, and making sure that the code is secure and efficient.&lt;/p&gt;

&lt;p&gt;So there's a lot of creativity and problem solving involved in what developers do, and it's not something that can be easily replaced by a machine... &lt;strong&gt;&lt;em&gt;Yet&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And I think that's the key thing here: LLMs might not be able to do everything &lt;strong&gt;&lt;em&gt;yet&lt;/em&gt;&lt;/strong&gt;, but they're getting better all the time. And as they get better, they're going to start to encroach on more and more of the tasks that we do.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's not just techies that are at risk
&lt;/h3&gt;

&lt;p&gt;If you follow some of the AI-related forums online, then you might have come across some of the images that some people are generating with AI. Or perhaps you've seen some of the videos or "movies" being created by AI.&lt;/p&gt;

&lt;p&gt;Some of them have pretty obvious flaws, like people with three arms, extra fingers, faces that are all distorted, or things like cars appearing out of nowhere or seemingly gliding sideways on the pavement. But some of them are pretty convincing, and it's getting harder and harder to tell what's real and what's not.&lt;/p&gt;

&lt;p&gt;But in the case of AI-generated code, AI-generated images, and AI-generated videos, they all have one thing in common: "AI Prompting".&lt;/p&gt;

&lt;h2&gt;
  
  
  AI and Prompting
&lt;/h2&gt;

&lt;p&gt;So what is AI Prompting?&lt;/p&gt;

&lt;p&gt;Well, in a nutshell, prompting, as its name implies, is simply the process of giving the AI a starting point - basically the instructions or directions to get started, and then letting it generate the rest of the content based on that starting point.&lt;/p&gt;

&lt;p&gt;You can prompt with a simple sentence like "Using typescript, provide me with code to connect to a T-SQL database using &lt;em&gt;[insert library name]&lt;/em&gt;", or you can prompt it with much more complex, multi-sentence, multi-paragraph instructions that go into a lot of specific detail.&lt;/p&gt;

&lt;p&gt;From what I've seen, the really well done AI-generated content is usually prompted with a lot of detail. The devil is, after all, in the details right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Creators - or Prompters?
&lt;/h2&gt;

&lt;p&gt;In the context of using AI to generate content - whether code, images, music, videos, etc., do you even really need to know how to create the content yourself when you have AI at your disposal?&lt;/p&gt;

&lt;p&gt;I think the answer is both yes, and no.&lt;/p&gt;

&lt;p&gt;I think for simpler AI generation, you can get away with having no knowledge or training in the area that you're generating the content for. But for more complex AI generation, you're going to need to have some kind of a basis in the area to understand or recognize when things go wrong, so that you can fix the issues.&lt;/p&gt;

&lt;p&gt;And so we come to my point: &lt;strong&gt;Developers, more and more, are going to need to learn how to prompt AI&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Developers
&lt;/h2&gt;

&lt;p&gt;In fact, this is exactly what my prediction revolves around - the need to understand &lt;em&gt;proper&lt;/em&gt; AI prompting.&lt;/p&gt;

&lt;p&gt;I see companies leaning more and more towards senior developers, and less so intermediate and junior developers. You can see it already in the postings for jobs online, where most postings are for senior devs, or "intermediate" devs with a resume (but not salary!) that reads like a senior dev.&lt;/p&gt;

&lt;p&gt;Now I'm not necessarily saying the shift right now is because of AI. Just that companies already started trimming the fat after covid, and as always, continue to look for any ways to reduce the workforce to save $$$, and AI is a possible way to do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Prediction
&lt;/h2&gt;

&lt;p&gt;Ok, so finally, my prediction is this:&lt;/p&gt;

&lt;p&gt;Going forward, as AI becomes more commonplace in tech companies, I believe that tech companies will stop, or at least limit, hiring beginner and intermediate devs. Instead, they'll hire only for a new role for a hybrid type of dev; one that has a lot of experience coding, but has also mastered the art of prompting AI to generate production-ready code.&lt;/p&gt;

&lt;p&gt;Essentially, a Senior dev/QA type of hybrid role. A bit like an AI code whisperer, if you will.&lt;/p&gt;

&lt;p&gt;The job of this "AI code whisperer" will essentially entail doing AI code prompting as the primary function, but being experienced and knowledgeable enough in multiple languages and frameworks to fix any issues/bugs, or possibly to prompt the AI to fix them when it's wrong. But the main criteria will be to know how to prompt the AI to architect and code the application from start to finish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Since when can humans predict the future?
&lt;/h3&gt;

&lt;p&gt;If there's one thing humans are really bad at though, it's making accurate predictions about the future, and I'm certainly no exception.&lt;/p&gt;

&lt;p&gt;So take my prediction with a grain of salt. I'm probably way off.&lt;/p&gt;

&lt;p&gt;But I think it's an interesting thought experiment, and I'm curious what other people's thoughts are on my "AI code whisperer" prediction.&lt;/p&gt;




&lt;p&gt;Am I crazy? Or do you think I might be onto something?&lt;/p&gt;

&lt;p&gt;Let me know what you think in the comments, and if you disagree, why?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>developers</category>
      <category>web</category>
    </item>
    <item>
      <title>Google's Decision to Effectively Kill-off Small Sites</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Tue, 02 Apr 2024 17:39:41 +0000</pubDate>
      <link>https://forem.com/mlaposta/googles-decision-to-effectively-kill-off-small-sites-28ea</link>
      <guid>https://forem.com/mlaposta/googles-decision-to-effectively-kill-off-small-sites-28ea</guid>
      <description>&lt;p&gt;I'm a relatively new blogger, having only started my blog around 6 months ago. I started my blog simply because I enjoy providing helpful information to others, especially when it's from things I've learned or experienced first hand.&lt;/p&gt;

&lt;p&gt;In fact, I love spreading knowledge and info so much that I used to write a monthly newsletter for the tech company I worked for, which went out to employees and management across several continents.&lt;/p&gt;

&lt;p&gt;Ok ok, I'm making it sound bigger than it really was... the employees I sent it to were limited to the same department as me. But it was still read by several hundred people, and when I decided to retire the newsletter, I received emails from a lot of the employees begging me to keep it going!&lt;/p&gt;

&lt;p&gt;Years later, at the same company but in a different department, I joined a team that wrote and maintained the knowledge base for the department, and I absolutely loved it. The employees that used the KB reached out to me regularly to thank me for making the info so well organized and easy to read/follow.&lt;/p&gt;

&lt;p&gt;So when I started my blog, I was excited to be able to share my knowledge and experiences with a wider audience, but I made two cardinal sins according to the "SEO gods":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;I didn't niche down enough&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I didn't focus on a single, specific topic&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Forgive me SEO gods, for I Have Sinned
&lt;/h2&gt;

&lt;p&gt;What did I do instead? I started my blog writing about &lt;strong&gt;&lt;em&gt;two of the most saturated topics on the web&lt;/em&gt;&lt;/strong&gt; ... &lt;strong&gt;travel&lt;/strong&gt;, and &lt;strong&gt;tech&lt;/strong&gt;. 🤦‍♂️&lt;/p&gt;

&lt;p&gt;Yeah, I'm that dumb.&lt;/p&gt;

&lt;p&gt;But see, I didn't know about the whole "niche down" thing when I started. I didn't even know there were "rules" to blogging and sites in general. I just wanted to write about things I enjoyed, and experiences I learned from. Pfff, what a fool I was! 🤪&lt;/p&gt;

&lt;p&gt;And even worse, I'm neither an expert on travel, nor an expert on any tech! I'm just a regular guy who enjoys both, and who has learned a lot about both over the years. But I'm not an expert in either, and I never claimed to be.&lt;/p&gt;

&lt;p&gt;In fact, with respect to tech, I consider myself a &lt;strong&gt;jack of all tech, master of none&lt;/strong&gt;. I've dabbled in a bunch of different computer languages, stacks, frameworks, environments, used various different DBs, etc. But I mean, that's kind of a requirement when you're in tech these days isn't it?&lt;/p&gt;

&lt;p&gt;But I digress...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Start of my Blog
&lt;/h2&gt;

&lt;p&gt;So, I started my blog, writing about things I've learned, things I've experienced, things I've found helpful, etc. And I've honestly been enjoying it and having a lot of fun with it. I've been enjoying the process of writing; of researching; of learning new things; and of course most of all - sharing my knowledge and experiences with others.&lt;/p&gt;

&lt;p&gt;And in that time, I saw my visibility in Google's SERPs slowly increase. I saw my articles start to rank for various keywords, and I saw my traffic slowly increase. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbrm0x7gkmelde4wectu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgbrm0x7gkmelde4wectu.png" alt="Google Analytics showing a slow increase in traffic" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I'm talking really embarrassingly low numbers here... like 10-20 visitors a day, which is pathetic even for a blog. But hey, it was something, and that was enough for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Google HCU Massacre
&lt;/h2&gt;

&lt;p&gt;But then late last year, I think maybe in October or November, I started hearing whispers on Reddit about a Google "HCU" causing some sites to lose 75% to 90% of their traffic. I didn't think much of it at the time, as I was still seeing my traffic slowly increase... until March of this year, when I lost &lt;strong&gt;&lt;em&gt;all of my traffic from Google&lt;/em&gt;&lt;/strong&gt; overnight. Instead of 1000+ page impressions a day, I started getting 1-10, and instead of 10-20 visits a day, Google is now bringing me a big fat 0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cjrmbrremlekzu6yo7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cjrmbrremlekzu6yo7b.png" alt="Google Analytics showing a massive drop in traffic" width="400" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  "If Your Site Was Affected, You're a Spammer!"
&lt;/h3&gt;

&lt;p&gt;As I've been browsing the subs on Reddit where I've seen this come up, I've seen a number of users commenting things to the effect of "&lt;em&gt;Good, the less spam affiliate garbage the better!&lt;/em&gt;", "&lt;em&gt;Your site/content was probably shit&lt;/em&gt;", "&lt;em&gt;Try writing actual decent, quality content for a change and maybe you'll rank!&lt;/em&gt; 🙄", etc. Basically lumping all sites with massacred traffic into one category: &lt;strong&gt;SPAM&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, I don't know much about SEO ... I had to look up what the heck a "SERP" was (Search Engine Results Page), forget every time what E-E-A-T (Experience, Expertise, Authoritativeness, and Trustworthiness) is supposed to mean literally 2 seconds after looking it up, don't know what short-tail or long-tail keywords are (and frankly don't really give a shit!), and don't use tools to determine my DA (domain authority), which I know is low. &lt;/p&gt;

&lt;p&gt;Why? Because I just want to write (hopefully decent and helpful) content, and hope it occasionally helps others.&lt;/p&gt;

&lt;h3&gt;
  
  
  But Who Has Actually Been Affected?
&lt;/h3&gt;

&lt;p&gt;Well, according a number of redditors, only affiliate-pushing spam sites, or sites with really bad content have been hit by this recent Google search update.&lt;/p&gt;

&lt;p&gt;But as other posters on Reddit have been noting, this HCU has actually been killing traffic to some of the bigger sites and players as well, ones with legit helpful content, and instead favouring the &lt;strong&gt;&lt;em&gt;really big players&lt;/em&gt;&lt;/strong&gt; like Reddit, Facebook, Quora (really?), and oddly, the news sites with those invasive, pop-up, non-stop adverts every paragraph or so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is This the End for Small Sites?
&lt;/h2&gt;

&lt;p&gt;As I've been reading the doom and gloom comments on Reddit about how only bad, poorly written or spammy content has been hit, I've tried to reflect on my own site...&lt;/p&gt;

&lt;p&gt;I do have some pretty poorly written content, which I wrote when I first started my blog, and which I've been meaning to go back and fix up.&lt;/p&gt;

&lt;p&gt;And I do have affiliate links in each of my posts - but only static ones, and only 1 or 2 per post.&lt;/p&gt;

&lt;p&gt;And I suppose my site isn't the nicest looking in the internet world, and has missing features I haven't found the time to add. But surely it's not that bad, is it?&lt;/p&gt;

&lt;p&gt;Maybe it is, maybe it isn't. I don't know. Maybe I'm guilty as charged, and deserve to be buried in the SERPs. But what I do know is that I share an opinion that others are starting to share as well: &lt;strong&gt;Google is no longer the search engine it once was&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google isn't the Only Search Engine in Town
&lt;/h2&gt;

&lt;p&gt;I actually mostly stopped using Google for search sometime last year, before all this HCU stuff started. But for me it was due to something I find extremely egregious: &lt;strong&gt;&lt;em&gt;Google's never ending, maddening, "I'm not a robot" captchas&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwge24gusn3ek3877d401.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwge24gusn3ek3877d401.png" alt="Google's annoying &amp;quot;I'm not a robot&amp;quot; captchas" width="461" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I get the captchas because I'm almost always on a VPN, and I'm almost always using Firefox. I also have a bunch of privacy and security add-ons installed, which probably doesn't help.&lt;/p&gt;

&lt;p&gt;But I'm pretty sure I'm not a robot (although I suppose that's debatable!), and I prefer not having to prove it to Google every 5 minutes or so. So I stopped using Google for search, and switched to using DuckDuckGo instead last year.&lt;/p&gt;

&lt;p&gt;And this isn't even the first time I've been burned by Google's decisions. If you're familiar at all with the &lt;a href="https://killedbygoogle.com/"&gt;Google Graveyard&lt;/a&gt;, you'll know that Google has a long history of killing off products and services that people have come to rely on. This has happened to me a number of times, in both a personal and professional capacity, and frankly it's getting old.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Go From Here
&lt;/h2&gt;

&lt;p&gt;So where do I go from here?&lt;/p&gt;

&lt;p&gt;I'm not entirely sure. I'm not going to stop writing, which I only do when time permits anyway. I'm just going to do like I was when I started my blog, and before I started trying to "SEO" it: write when I can, and hope that what I write is helpful to someone.&lt;/p&gt;




&lt;p&gt;Have you been affected by the latest HCU? Are you seeing massive drops/increases in traffic? Are you considering moving away from Google in general?&lt;/p&gt;

&lt;p&gt;Let me know in the comments below!&lt;/p&gt;

</description>
      <category>google</category>
      <category>discuss</category>
      <category>web</category>
    </item>
    <item>
      <title>How to Redirect URLs with Netlify</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Sat, 09 Mar 2024 15:30:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/how-to-redirect-urls-with-netlify-22ah</link>
      <guid>https://forem.com/mlaposta/how-to-redirect-urls-with-netlify-22ah</guid>
      <description>&lt;p&gt;In light of a &lt;a href="https://wheresbaldo.dev/tech/netlify/is-hosting-on-netlify-going-to-bankrupt-you"&gt;recent post I made about Netlify&lt;/a&gt;, I hesitated about whether or not to make this post. In the end, I figured it might still be useful to someone out there, and so I'm posting it after all. Hopefully someone finds it helpful...&lt;/p&gt;




&lt;p&gt;In a recent post, I outlined &lt;a href="https://wheresbaldo.dev/tech/nextjs/how-to-redirect-urls-in-next-js"&gt;5 different ways you can redirect URLs in Next.JS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What I didn't mention in that post, was that most of those methods don't work with my particular setup. It seems that my package manager setup causes a bunch of issues with Netlify, which is where the site I use the redirects with is hosted.&lt;/p&gt;

&lt;p&gt;Sigh.&lt;/p&gt;

&lt;p&gt;So, after attempting several ways to get the redirects working in Next.js, and failing, I had to find another way.&lt;/p&gt;

&lt;p&gt;Thankfully, Netlify has two built-in methods for URL redirection, and they're super simple to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Using the &lt;code&gt;_redirects&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;The first, and easiest method, is to use a &lt;code&gt;_redirects&lt;/code&gt; file. This is a simple text file that you can create in the &lt;strong&gt;root&lt;/strong&gt; of your &lt;strong&gt;&lt;em&gt;site&lt;/em&gt;&lt;/strong&gt;, which contains a list of one-line URL redirects, in a TSV-like format.&lt;/p&gt;

&lt;p&gt;Now, I say &lt;strong&gt;root&lt;/strong&gt; of your &lt;strong&gt;&lt;em&gt;site&lt;/em&gt;&lt;/strong&gt;, because I'm talking about the root of your website, and not the root of your project folder. So depending on your particular setup, this will likely differ.&lt;/p&gt;

&lt;p&gt;According to Netlify, this is your "Publish" directory in your site's settings, however for me that wasn't the case.&lt;/p&gt;

&lt;p&gt;The "Publish" directory in my Netlify settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnpubm2hrelhy2jfgw3l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnpubm2hrelhy2jfgw3l.png" alt='My Netlify "Publish" directory' width="475" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm using Next.js, and what ends up being the root published directory for my static files is actually the &lt;code&gt;/public/&lt;/code&gt; folder in my project source. So, I had to create the &lt;code&gt;_redirects&lt;/code&gt; file in the &lt;code&gt;public&lt;/code&gt; folder instead. Again, this may differ for you, so you may need to experiment a bit to find the correct location for your &lt;code&gt;_redirects&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Syntax
&lt;/h3&gt;

&lt;p&gt;The basic syntax is pretty straight forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/old-page-1  /new-page-1
/old-page-2  /new-page-2
/old-page-3  https://www.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line contains the old URL, followed by a space or tab (or multiple spaces or tabs), and then the new URL, which can be a local or external redirect. Easy peasy!&lt;/p&gt;

&lt;h3&gt;
  
  
  Wildcards and Placeholders
&lt;/h3&gt;

&lt;p&gt;You can also use wildcards, which is super handy for redirecting entire paths to a single new page, or for capturing parts of the old URL and appending them to the new URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/old-directory-1/* /new-page
/old-directory-2/* /new-directory/:splat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the 1st line above, we want to redirect any pages within &lt;code&gt;old-directory-1&lt;/code&gt; to a single new page - perhaps a custom 404 page, or a new landing page.&lt;/p&gt;

&lt;p&gt;On the 2nd line, we're using the &lt;code&gt;:splat&lt;/code&gt; keyword, which is a special variable that captures the rest of the URL after the wildcard in the old URL, and appends it to the new URL.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you use the asterisk &lt;code&gt;*&lt;/code&gt; wildcard, it must appear at the end of the old URL after the final '&lt;code&gt;/&lt;/code&gt;', and so of course the &lt;code&gt;:splat&lt;/code&gt; must also appear at the end of the new URL after the final '&lt;code&gt;/&lt;/code&gt;'.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rules are matched in order, so if you have a more specific rule that comes after a more general rule, the specific rule will be ignored. For example, in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/old-directory-1/*              /new-directory/:splat
/old-directory-1/specific-page  /new-specific-page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... because we placed the general rule above the specific rule, the specific rule will never be matched. So, if you have a specific rule, make sure it comes before any general rules that might match it.&lt;/p&gt;

&lt;p&gt;If on the other hand you wanted to capture and keep a particular segment of the URL, you could use placeholder variables instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/some-folder/:month/*  /some-other-folder/:month/another-folder/:splat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, we're capturing the &lt;code&gt;:month&lt;/code&gt; placeholder from the old URL, and copying it to the new URL. So we're able to change the directories in the URL, while carrying over the month segment.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP Status Codes
&lt;/h3&gt;

&lt;p&gt;Now in the above examples, we included only the old and new URLs, but we might also want to specify the HTTP status of the redirect. The HTTP status code is optional, and if not specified, Netlify will simply default to a 301 (permanent) redirect.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Permanent redirect
/old-page-1   /new-page-1            301

# Temporary redirect
/old-url-1/*  /new-url-1/:splat      302

# URL rewrite
/visible-url  /actual-url/page.html  200

# Not found
/old-url-2    /custom-error-page     404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, for the first 2 redirects, we're just adding in the HTTP status code. In these 2 cases, the URL displayed in the browser address bar will change to that of the new URL.&lt;/p&gt;

&lt;p&gt;For the 3rd redirect, we're using a 200 status code, which is actually a URL &lt;em&gt;rewrite&lt;/em&gt;, not a redirect. And finally, for the 4th redirect, we're using a 404 status code, which will show a custom error page.&lt;/p&gt;

&lt;p&gt;In the case of both the 200 and 404 status codes, the URL in the address bar of the browser will &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; change, only the content of the page will.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you want to add comments to the &lt;code&gt;_redirects&lt;/code&gt; file, you can do so by prefixing the line with a &lt;code&gt;#&lt;/code&gt; character, just like in a bash script.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Other Options
&lt;/h3&gt;

&lt;p&gt;Netlify's &lt;code&gt;_redirects&lt;/code&gt; file also supports a few other options, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an '&lt;code&gt;!&lt;/code&gt;' after the status code to force a redirect, even if a path exists at the old URL&lt;/li&gt;
&lt;li&gt;query parameters (ex: &lt;code&gt;?src=xx&lt;/code&gt; or &lt;code&gt;?src=xx&amp;amp;id=yy&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;redirects by country or language&lt;/li&gt;
&lt;li&gt;redirects based on cookies present in the request headers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you are utilizing Netlify's auth systems, you can also redirect based on the user's authenticated role.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Using the &lt;code&gt;netlify.toml&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;The second method for URL redirection with Netlify is to use a &lt;code&gt;netlify.toml&lt;/code&gt; file. This is a configuration file that you can use to specify a bunch of settings for your site, including redirects. So if you're already using the &lt;code&gt;netlify.toml&lt;/code&gt; file, you can add your redirects to this file instead of using the specific &lt;code&gt;_redirects&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;netlify.toml&lt;/code&gt; file, instead of having each redirect on it's own line, you'll add each redirect under it's own section, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[redirects]]&lt;/span&gt;
  &lt;span class="py"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/old-page-1"&lt;/span&gt;
  &lt;span class="py"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/new-page-1"&lt;/span&gt;
  &lt;span class="py"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt;

&lt;span class="nn"&gt;[[redirects]]&lt;/span&gt;
  &lt;span class="py"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/old-url-2/*"&lt;/span&gt;
  &lt;span class="py"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/new-url-2/:splat"&lt;/span&gt;
  &lt;span class="py"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;

&lt;span class="nn"&gt;[[redirects]]&lt;/span&gt;
  &lt;span class="py"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/old-url-3"&lt;/span&gt;
  &lt;span class="py"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/custom-error-page"&lt;/span&gt;
  &lt;span class="py"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that like with the &lt;code&gt;_redirects&lt;/code&gt; file, rules in the &lt;code&gt;netlify.toml&lt;/code&gt; file are matched in order. So if you have a more specific rule that comes after a more general rule, the specific rule will be ignored.&lt;/p&gt;

&lt;p&gt;Also note that while the &lt;code&gt;netlify.toml&lt;/code&gt; file allows for the same options as using the &lt;code&gt;_redirects&lt;/code&gt; file, albeit with a different syntax, it also allows for setting redirect headers, which you can't do with the &lt;code&gt;_redirects&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;For full details on what redirect options Netlify supports, both in the &lt;code&gt;_redirects&lt;/code&gt; file as well as the &lt;code&gt;netlify.toml&lt;/code&gt; file, refer to their official documentation on &lt;a href="https://docs.netlify.com/routing/redirects/redirect-options/"&gt;Redirects and Rewrites&lt;/a&gt;, which gives many more details than I've covered here.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I've been using a &lt;code&gt;_redirects&lt;/code&gt; file for a while now, and it not only works amazing well, but it's basically instantaneous. However, I'd suggest that if you are using a framework with its own redirect system, then you may want to use that as a 1st option. By keeping your redirects as part of your codebase, you're not locked in to specific vendor, like Netlify.&lt;/p&gt;

&lt;p&gt;On the other hand, if you've found yourself in a situation like I did, where the built-in redirect system of your framework doesn't work with your hosting provider and specific setup, then Netlify's built-in redirect system is a great alternative. Heck, even a lifesaver! 😜&lt;/p&gt;

</description>
      <category>netlify</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Is Hosting on Netlify Going to Bankrupt you?</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Wed, 28 Feb 2024 15:00:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/is-hosting-on-netlify-going-to-bankrupt-you-4d1d</link>
      <guid>https://forem.com/mlaposta/is-hosting-on-netlify-going-to-bankrupt-you-4d1d</guid>
      <description>&lt;p&gt;On Tuesday, February 27, I was casually browsing Reddit, as I often do, when I stumbled on a slightly alarming post in the &lt;strong&gt;r/webdev&lt;/strong&gt; subreddit. The post was titled "Netlify just sent me a $104K bill for a simple static site".&lt;/p&gt;

&lt;p&gt;You can read &lt;a href="https://www.reddit.com/r/webdev/comments/1b14bty/netlify_just_sent_me_a_104k_bill_for_a_simple"&gt;the full post here on Reddit&lt;/a&gt; for the OP's story if you didn't catch it, but in short, OP's essentially unknown site got hit by a sudden onslaught of DDoS traffic, &lt;strong&gt;racking them up a cool $104,000 bill from Netlify&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvvjr9lzjdewmmic0ajd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvvjr9lzjdewmmic0ajd.jpg" alt="OP's Netlify bill" width="800" height="222"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now according to Netlify, &lt;strong&gt;&lt;em&gt;normally&lt;/em&gt;&lt;/strong&gt;, they can detect and mitigate DDoS attacks, and the OP's case was simply an anomaly. But instead of immediately waiving the bill, they very nonchalantly suggested they'd only charge him 5% of the bill - only $5,200 - basically a steal right?? 🙄&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdop6dce5v6rxgxmkr0wd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdop6dce5v6rxgxmkr0wd.jpg" alt="Netlify's initial response" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is, until some commenters on Reddit suggested OP post the story to Hacker News, &lt;a href="https://news.ycombinator.com/item?id=39520776"&gt;which OP did&lt;/a&gt;, and then Netlify suddenly changed their tune.&lt;/p&gt;

&lt;p&gt;After the story went viral, &lt;a href="https://news.ycombinator.com/item?id=39521986"&gt;the CEO commented on the same Hacker News thread&lt;/a&gt; that the fees would be waived, and apologized that the support team didn't handle the situation better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjnj6hzbz1qz7kcy5o49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjnj6hzbz1qz7kcy5o49.png" alt="Netlify CEO's initial response" width="763" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But a lot of damage had already been done, and many Redditors felt that Netlify's response was too little, too late. They commented that they'd lost their trust in the company, and that they had already started migrating their sites away from Netlify, and would never use them again. Others who were considering using Netlify in the future, said they'd now be looking elsewhere.&lt;/p&gt;

&lt;p&gt;I've been a Netlify user for a few years now, and while I can't say I find their service perfect as it's missing some pretty crucial support for some things I use, it's still been a pretty good experience overall. And for someone like me (and like the OP), with just a small-time relatively unknown site generating very little traffic every month (basically nothing), their free tier has been a godsend. Or well, at least I thought it was, until I read this story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Concern
&lt;/h2&gt;

&lt;p&gt;Ok, so what's the actual reason, assuming you didn't venture off to read OP's story, that a free-tier site was able to rack up a massive bill in a very short amount of time?&lt;/p&gt;

&lt;p&gt;Well, I already mentioned it was related to a sudden onslaught of DDoS traffic, but the real concern here is that &lt;strong&gt;Netlify doesn't shut down your site&lt;/strong&gt; when the traffic surges.&lt;/p&gt;

&lt;p&gt;In fact, not only do they not shut down the traffic to your site, but apparently OP only received a single email from Netlify about "Extra usage package purchased"! And that was it. No warning, no nothing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvcaa300k991gl9p9i9i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvcaa300k991gl9p9i9i.png" alt="OPs comment about email from Netlify" width="737" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Netlify apparently has agreements in place with paid tiers, but free tiers offer no such provisions. So, if you're on the free tier, and you get hit by a DDoS attack, you're basically screwed. But don't worry, they'll give you a good discount!&lt;/p&gt;

&lt;p&gt;Some commenters on Reddit even noted - with such a policy in place - it's almost like Netlify is &lt;strong&gt;&lt;em&gt;encouraging&lt;/em&gt;&lt;/strong&gt; DDoS attacks on free-tier sites, as they'd be the only ones who'd benefit from it.&lt;/p&gt;

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

&lt;p&gt;As I noted at the beginning of this post, many Redditors have already started migrating their sites away from Netlify. But to say Netlify is alone with this policy would be inaccurate.&lt;/p&gt;

&lt;p&gt;It seems that a number of other popular hosts with free tiers also don't offer a kill-switch for traffic surges, so moving over to another host may not necessarily solve the problem.&lt;/p&gt;

&lt;p&gt;Reading the fine print has yet again shown to be crucial, and I'm just as guilty as the others for not doing so!&lt;/p&gt;

&lt;p&gt;After some more backlash, Netlify's CEO &lt;a href="https://news.ycombinator.com/item?id=39522139"&gt;posted a follow-up comment&lt;/a&gt; on the same Hacker News thread, stating that they'd be reviewing their policies and making changes to ensure that this kind of situation doesn't happen again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgyll61tvhbhni2vqe6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgyll61tvhbhni2vqe6g.png" alt="Netlify CEO's follow-up response" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now while that's a bit of a relief, I wonder how long it'll take for them to actually implement these changes, and if they'll be enough to win back the trust of those who've already left.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I'm not entirely sure if it's enough for me, but I guess only time will tell. As it stands, I'm still considering my options.&lt;/p&gt;

&lt;p&gt;I'm not sure if I'll be moving my sites away from Netlify just yet, but I'm definitely going to be keeping a closer eye on my traffic and usage from now on.&lt;/p&gt;

&lt;p&gt;I've also learned that I need to be more vigilant with the fine print of the services I use, and I hope you've learned the same from this post.&lt;/p&gt;

&lt;p&gt;What are your thoughts on this? Are people freaking out too much over this? Have you been affected by a similar situation with Netlify or another host?&lt;/p&gt;

&lt;p&gt;Let me know in the comments below.&lt;/p&gt;

</description>
      <category>netlify</category>
      <category>hosting</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Fix Path Name Mismatches in Local &amp; Remote Git Repos</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Thu, 22 Feb 2024 14:30:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/fix-path-name-mismatches-in-local-remote-git-repos-25kd</link>
      <guid>https://forem.com/mlaposta/fix-path-name-mismatches-in-local-remote-git-repos-25kd</guid>
      <description>&lt;p&gt;If you've ever written any computer code, you've undoubtedly (inadvertently) introduced bugs in your code. I mean, are you even a developer if you haven't? 😂&lt;br&gt;
Hell, even ChatGPT makes bugs in it's code suggestions, and it's an AI with access to the best code out there!&lt;/p&gt;

&lt;p&gt;But how about when something isn't working as it should on your site, but all of your code checks out fine? 🤔&lt;/p&gt;

&lt;p&gt;Hence ...&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Originally, for no reason in particular, I was naming all of my blog post filenames using &lt;strong&gt;Pascal case&lt;/strong&gt;. Or in other words, the first letter of each word was capitalized.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;Fix-File-And-Folder-Name-Mismatches-Between-Remote-And-Local-Git-Repo&lt;/code&gt; or something along those lines.&lt;/p&gt;

&lt;p&gt;I later realized that this was causing redirects on the live server to lowercase URLs, and sometimes (though oddly not always) resulting in 404 errors. Even when there weren't any obvious, visible issues, I was still getting warnings on some analytics tools and search consoles.&lt;/p&gt;

&lt;p&gt;I left things as is initially, but decided a while later that it would probably be safer to just convert everything, file and folder names, to all lowercase. &lt;/p&gt;
&lt;h3&gt;
  
  
  Initial (Faulty) Git Update
&lt;/h3&gt;

&lt;p&gt;I did this by renaming the files and folders in bash the manual way ... one by one (not very smart I'll admit), and &lt;strong&gt;&lt;em&gt;then&lt;/em&gt;&lt;/strong&gt; committing the changes to Git.&lt;/p&gt;

&lt;p&gt;I double-checked all of the files in my local repo, and ran a test build, and everything was looking good. I also checked the live site after committing and pushing the changes to my remote repo, and rebuilding the site. No issues that I could see there, so I thought I was in the clear, but I didn't notice one of the posts was still showing up with the old Pascal-case filename.&lt;/p&gt;

&lt;p&gt;So after scratching my head for a while and thinking I was going mad, I added the .toLowerCase() method to some of the code to make sure it forced everything to lower case. Somehow though, that post in particular was still showing up with the old Pascal case filenames!&lt;/p&gt;

&lt;p&gt;I was super confused, and I couldn't figure out how this was happening until finally, recently, &lt;strong&gt;I checked my remote Git repo and realized one of the paths was &lt;em&gt;not&lt;/em&gt; synched up properly with my local repo!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Apparently, the old folder name had remained cached in Git, despite me committing and pushing the changes to my remote repo. So I had to figure out how to fix this, but hey, at least I'd finally found the problem! 🥴&lt;/p&gt;
&lt;h2&gt;
  
  
  First Attempt to Search for a Solution
&lt;/h2&gt;

&lt;p&gt;At first I tried, unsuccessfully, to &lt;strong&gt;&lt;em&gt;re&lt;/em&gt;&lt;/strong&gt;-rename the folder using git ... as in, rename it back to Pascal-case and then back to all lowercase. But nope, Git was having none of that.&lt;/p&gt;

&lt;p&gt;So I did some Googling, but was mostly finding posts about how to easily convert the local filenames to a different case, but not how to fix the issue of the remote repo not matching the local repo.&lt;/p&gt;

&lt;p&gt;I did find much smarter ways to convert the filenames to lowercase though, so that was a plus... albeit too late for me since my filenames were already converted. Hah! 😅&lt;/p&gt;

&lt;p&gt;I also found some posts about how to fix the issue of Git not recognizing case changes, but that wasn't my issue. I had already committed and pushed the changes to my remote repo, and the changes were showing up in my local repo. My issue was with the remote repo not being synchronized with the local repo.&lt;/p&gt;
&lt;h2&gt;
  
  
  Second Attempt to Search for a Solution
&lt;/h2&gt;

&lt;p&gt;I thought "Hey, I've got a whole post for &lt;a href="https://wheresbaldo.dev/tech/git/git-cheat-sheet"&gt;some of the most common git commands&lt;/a&gt; that I wrote a while back, so I'll just skim over it and see if I already wrote about the issue and just forgot!"&lt;/p&gt;

&lt;p&gt;But alas, I definitely did not include anything about fixing this particular issue in the cheat sheet. 😕&lt;br&gt;
And it makes sense I guess, since I'd never run into this issue before, and I'd never even heard of it before.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Actual Working Solution
&lt;/h2&gt;

&lt;p&gt;Eventually though, &lt;a href="https://stackoverflow.com/questions/17683458/how-do-i-commit-case-sensitive-only-filename-changes-in-git"&gt;I found this StackOverflow post&lt;/a&gt; that had a reply from &lt;a href="https://stackoverflow.com/users/1402481/andrewvergel"&gt;@andrewvergel&lt;/a&gt; with a super simple way to fix the issue:&lt;/p&gt;

&lt;p&gt;From bash, run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
git add &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Don't forget the &lt;code&gt;"."&lt;/code&gt; at the end of the commands!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After running the above two git commands, you should review the changes to make sure everything looks good before committing and pushing the changes to your remote repo.&lt;/p&gt;

&lt;p&gt;It's likely some other files will be added to the index, so you'll want to make sure those are files you want to add. In my case, I did in fact have a bunch of files added that I didn't want to commit, so I had to unstage them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To unstage a file, simply run the following command:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;git restore &lt;span class="nt"&gt;--staged&lt;/span&gt; &amp;lt;file&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;Then, once you're sure only the necessary files are added, do your normal commit, and push the changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Fixing file name casing"&lt;/span&gt;
git push origin &amp;lt;branch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  "git rm -r --cached ."
&lt;/h4&gt;

&lt;p&gt;Per &lt;a href="https://git-scm.com/docs/"&gt;the Git docs&lt;/a&gt;, the &lt;code&gt;--cached&lt;/code&gt; flag in the &lt;code&gt;git rm&lt;/code&gt; command will remove the files from it's internal index, but &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; from the working tree. &lt;/p&gt;

&lt;h4&gt;
  
  
  "git add --all ."
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;--all&lt;/code&gt; flag in the &lt;code&gt;git add&lt;/code&gt; command will add, modify, and remove index entries to match the working tree.&lt;/p&gt;

&lt;p&gt;If that's not super clear though, perhaps &lt;a href="https://stackoverflow.com/users/4484799/uriahs-victor"&gt;@Uriahs Victor's&lt;/a&gt; response in the same post is a bit easier to understand:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What this command actually does is deletes the cached version of the file/folder names that git thought still existed. So it will clear its cache but leave everything in the current folder (where you've made your changes locally) but it will see that those other wrong case folders/files do not exist anymore so will show them as deleted in git status. Then you can push up to GitHub and it will remove the folders/files with wrong cases.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;And that's basically it!&lt;/p&gt;

&lt;p&gt;So to recap, if you notice any weird file-case issues on your live site that aren't appearing in your dev environment, or vice versa, you can run the following commands from bash to sync your local and remote repos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
git add &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Fixing file name casing"&lt;/span&gt;
git push origin &amp;lt;branch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the above git commands should flush out any cached mismatches and fix your issue.&lt;/p&gt;

&lt;p&gt;Hope that helps someone else out there!&lt;/p&gt;

</description>
      <category>git</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>Limit the Number of Pre-rendered Pages in Next.js</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Wed, 14 Feb 2024 14:30:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/limit-the-number-of-pre-rendered-pages-in-nextjs-301d</link>
      <guid>https://forem.com/mlaposta/limit-the-number-of-pre-rendered-pages-in-nextjs-301d</guid>
      <description>&lt;p&gt;They say that history repeats itself, and it really does ring true for so many things in our lives.&lt;/p&gt;

&lt;h3&gt;
  
  
  A little bit of Back in the Day ...
&lt;/h3&gt;

&lt;p&gt;Back when the "World Wide Web" first started getting popular, websites were static through and through, because reactive and dynamic web tech hadn't been invented yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9bwm5bphjec9915i9mx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9bwm5bphjec9915i9mx.jpg" alt="Geocities in 1996" width="650" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter the dynamic web ...
&lt;/h3&gt;

&lt;p&gt;Then came the dynamic web, and with it, the ability to build sites that were reactive and interactive.&lt;/p&gt;

&lt;p&gt;Initially this was done with server-side environments/languages like Java, PHP, ASP, JSP, etc. combined with JavaScript on the client-side, but eventually client-side JavaScript libraries like jQuery started to appear. There was even Flash ... remember that? 😂&lt;/p&gt;

&lt;p&gt;But it wasn't really until frameworks like Angular, and libraries like React and Vue came along, that the pendulum swung the other way, and static sites were now seen as old and outdated.&lt;/p&gt;

&lt;p&gt;This was a huge leap forward, and it was amazing to see what could be done with this new technology.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjgv0sfa3971hx790ir9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjgv0sfa3971hx790ir9.jpg" alt="Angular, React.js, &amp;amp; Vue.js logos" width="600" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new hotness was to build your site as a single-page application (SPA), and let the client-side JavaScript handle all the rendering. This was an amazing way of doing things, but the issue with this approach is that it's not very SEO friendly, and for very large sites, the initial load time can be quite long.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hello meta-frameworks ...
&lt;/h3&gt;

&lt;p&gt;More recently, with the advent of meta-frameworks like Next.js, Remix.js, Astro, et al., and the ability to pre-render your site at build time, static sites are back on the menu once again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5uppf8ltdp3955hp0a7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5uppf8ltdp3955hp0a7.jpg" alt="Next.js, Remix.js, and Astro" width="600" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But there can be issues with this approach as well, especially for very large sites: pre-rendering your entire site at build time can be very costly, both in terms of build time, as well as the actual dollar cost of running the build.&lt;/p&gt;

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

&lt;p&gt;I stumbled on a reddit thread (or maybe it was a Stackoverflow question?) the other day, where someone was asking about migrating a site with over 250,000 pages to Next.js. I think it was an e-comm site or something, but I can't seem to find the thread again. Anyway, the gist of the question was that they were concerned about the build time and cost of pre-rendering all those pages.&lt;/p&gt;

&lt;p&gt;Now I have no idea how long it would take to pre-render 250,000+ pages, but I can promise you it wouldn't be quick. If I could make a rough estimate, I'd wager you'd be looking at about 2 hours or so, give or take.&lt;/p&gt;

&lt;p&gt;Now that's bad enough, but the real issue is that you'd have to do that &lt;strong&gt;&lt;em&gt;every time you update the site&lt;/em&gt;&lt;/strong&gt;. And if you're running a business, you're going to want to update your site fairly regularly, so that's a lot of time and money spent (wasted) on builds.&lt;/p&gt;

&lt;h3&gt;
  
  
  In an ideal world...
&lt;/h3&gt;

&lt;p&gt;Ideally, you'd be able to do the initial pre-rendering of all your pages in Next.js on your very first build, and then only re-build the pages that have changed since the last build on subsequent builds. This would save a ton of time and money, and would be a much more efficient way of doing things.&lt;/p&gt;

&lt;p&gt;I searched online to see if Next.js had this ability, but couldn't find anything. Part of the issue is that with cloud hosts like Vercel and Netlify, your code is pulled fresh from your repo every time you build, so there's no way to know which pages have changed since the last build.&lt;/p&gt;

&lt;p&gt;I did see ISR (Incremental Static Regeneration) mentioned a few times, but that's not really what I was looking for. ISR is great for pages that are updated frequently, but it doesn't help with the initial build time.&lt;/p&gt;

&lt;p&gt;So what's a Next.js dev to do?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution (well ... sort of)
&lt;/h2&gt;

&lt;p&gt;Unfortunately I don't have a solution for only re-rendering pages that have changed since the last build, &lt;strong&gt;but there is a way to limit the number of pages that are pre-rendered at build time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So while limiting the number of pre-built pages isn't perfect, it's still a good compromise, and might save you time and money in the long run.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Gist
&lt;/h3&gt;

&lt;p&gt;The gist of this solution is to only spit out the pages that you want pre-rendered in the &lt;code&gt;getStaticPaths&lt;/code&gt; function of your page. This is the function that tells Next.js which pages to pre-render at build time.&lt;/p&gt;

&lt;p&gt;You could base it on which of your pages are the most popular, or which ones are the most likely to be visited, or you could just pick a random selection of pages. Alternatively, if you're dealing with a blog for example, you could just pre-render the 50-100 most recent posts, the rest of which would get rendered automatically at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Code
&lt;/h3&gt;

&lt;p&gt;The following is an (incomplete) example of how you could achieve 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="c1"&gt;// pages/[slug].js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAllPosts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getPostBySlug&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="s1"&gt;../../lib/api&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&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;h1&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;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticPaths&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;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAllPosts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Sort posts by published date, descending&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sortedPosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;post2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;post1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;post2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Limit the number of pre-rendered pages to 50&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sortedPosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&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;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="nx"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blocking&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPostBySlug&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="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Check to make sure the page exists, and return a 404 if it doesn't&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&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="na"&gt;notFound&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;post&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;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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="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;Now, I've made some assumptions here, namely that you have a &lt;code&gt;lib/api&lt;/code&gt; TS or JS file that contains functions for getting all your posts, and getting a single post by slug. I've also assumed that your posts have a &lt;code&gt;published&lt;/code&gt; date, and a &lt;code&gt;slug&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;In my own code, I do the sorting by date in the &lt;code&gt;getAllPosts()&lt;/code&gt; function, but I included it here in &lt;code&gt;getStaticPaths()&lt;/code&gt; for clarity.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Points
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In this example, we're using the &lt;code&gt;slice()&lt;/code&gt; method to limit the number of pages that are pre-rendered to &lt;em&gt;50&lt;/em&gt;. You could also use &lt;code&gt;filter()&lt;/code&gt; to only pre-render pages that meet certain criteria, or any other array-manipulation method you like.&lt;/p&gt;

&lt;p&gt;In my own code, like with the sorting, I actually slice the array in the &lt;code&gt;getAllPosts()&lt;/code&gt; function, but included it here in &lt;code&gt;getStaticPaths()&lt;/code&gt; once again for clarity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;fallback: 'blocking'&lt;/code&gt; key in the return object in &lt;code&gt;getStaticPaths()&lt;/code&gt;. This tells Next.js to render any pages that weren't pre-rendered at build time &lt;strong&gt;&lt;em&gt;at runtime instead&lt;/em&gt;&lt;/strong&gt;, since they haven't yet been rendered. &lt;/p&gt;

&lt;p&gt;Alternatively, you could set &lt;code&gt;fallback&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, which will also render the extra pages at runtime, &lt;a href="https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-true"&gt;but will show a fallback page&lt;/a&gt; while the page is being rendered, whereas setting it to &lt;code&gt;'blocking'&lt;/code&gt; &lt;a href="https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-blocking"&gt;will block loading until the page is rendered&lt;/a&gt;, similar to SSR.&lt;/p&gt;

&lt;p&gt;I'm using &lt;code&gt;'blocking'&lt;/code&gt; here, because I don't want to show a fallback page, and I don't want to show a loading indicator either. I'd rather just have the page load instantly, even if it takes a few seconds to render, but that's just my preference.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;notFound: true&lt;/code&gt; key in the return object in &lt;code&gt;getStaticProps()&lt;/code&gt;. This tells Next.js to return a 404 page if the page doesn't exist. When &lt;code&gt;fallback&lt;/code&gt; is set to false, Next.js knows to issue a 404 for any pages that aren't in the &lt;code&gt;paths&lt;/code&gt; array, but when &lt;code&gt;fallback&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;'blocking'&lt;/code&gt;, it doesn't know which paths/files exist or not, so you have to explicitly tell it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Result
&lt;/h3&gt;

&lt;p&gt;So what does this look like in practice?&lt;/p&gt;

&lt;p&gt;Well, if you visit a page that's in the &lt;code&gt;paths&lt;/code&gt; array, because it was pre-rendered at &lt;strong&gt;&lt;em&gt;build time&lt;/em&gt;&lt;/strong&gt;, you'll see the page load instantly.&lt;/p&gt;

&lt;p&gt;On the other hand, if you visit a page that isn't in the &lt;code&gt;paths&lt;/code&gt; array, it will be rendered at &lt;strong&gt;&lt;em&gt;runtime&lt;/em&gt;&lt;/strong&gt;, and you &lt;em&gt;might&lt;/em&gt; see a loading indicator (or a fallback page if you set &lt;code&gt;fallback: true&lt;/code&gt;) while the page is being rendered, depending on the size of the page and the speed of your connection.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The rendering of non-pre-rendered pages &lt;strong&gt;will only happen once&lt;/strong&gt;, after which the page will be cached for future visits. So it's not like you'll be waiting for the page to render every time you visit it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So there you have it. While this solution isn't perfect, it's a good compromise, and might save you time and money in the long run.&lt;/p&gt;

&lt;p&gt;Know of a way in Next.js to only re-render pages that have changed since the last build? Please, let me know in the comments!! I'd love to hear about it. 😁&lt;/p&gt;

</description>
      <category>netlify</category>
      <category>staticwebapps</category>
      <category>nextjs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Understanding PostgreSQL RLS, and When to Use it</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Mon, 05 Feb 2024 16:20:28 +0000</pubDate>
      <link>https://forem.com/mlaposta/understanding-postgresql-rls-and-when-to-use-it-1le</link>
      <guid>https://forem.com/mlaposta/understanding-postgresql-rls-and-when-to-use-it-1le</guid>
      <description>&lt;p&gt;Somewhere around 3rd or 4th quarter 2023, I was starting to look into the requirements of a project I needed to start in the new year. I was trying to decide on which stack I wanted to use, including the database, which of course is a major part of the stack.&lt;/p&gt;

&lt;p&gt;Now typically in the past I've used either MySQL or MS SQL Server, as that was either what was available to me, or what I was most familiar with. I was really leaning towards a cloud-based solution this time round though, and I'd been hearing so much buzz about Supabase, that I wanted to see if it made sense for me to use in my project.&lt;/p&gt;

&lt;p&gt;One of the main things I saw talked about, one of Supabase's main selling points, was that its underlying DB is &lt;a href="https://www.postgresql.org/about/"&gt;PostgreSQL&lt;/a&gt;, which allows restricting access via RLS, or Row Level Security.&lt;/p&gt;

&lt;p&gt;But what exactly is RLS? And why is it so desirable?&lt;/p&gt;

&lt;h2&gt;
  
  
  What is PostgreSQL Row-Level Security?
&lt;/h2&gt;

&lt;p&gt;Row-Level Security (RLS) in a database is a feature that allows you to restrict access to individual rows in a database table based on user-defined policies.&lt;/p&gt;

&lt;p&gt;These policies are defined at the table level like standard table accesses, but they allow you to restrict which rows a user can access based on specific conditions. RLS applies to all queries that access the table, including SELECT, INSERT, UPDATE, and DELETE statements.&lt;/p&gt;

&lt;p&gt;For my purposes, I'd be linking the access control via an authenticated user's ID or role, but you could use other conditions as well, like IP address, or even the time of day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is PostgreSQL Row-Level Security such a big deal?
&lt;/h2&gt;

&lt;p&gt;RLS is important because it allows you to restrict access to &lt;strong&gt;rows&lt;/strong&gt; in a database table based on access policies. This adds an additional layer of security to your database by allowing you to implement access control at the granularity of individual rows rather than just at the table-level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some RLS Examples
&lt;/h3&gt;

&lt;p&gt;Full details of PostgreSQL's RLS policies &lt;a href="https://www.postgresql.org/docs/current/sql-createpolicy.html"&gt;can be found here&lt;/a&gt;, but the basic format for creating a policy is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;policy_name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;action&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;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;&amp;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;WITH&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Example 1: Bypassing RLS on a table when RLS is enabled
&lt;/h4&gt;

&lt;p&gt;I've included this here just for the sake of demonstrating how the &lt;strong&gt;&lt;em&gt;using&lt;/em&gt;&lt;/strong&gt; clause works. In reality, you wouldn't enable RLS on a table in the first place if you were going to bypass it, but just to give you an idea of what the syntax would look like, here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;bypass_rls_policy&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;some_table&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the &lt;code&gt;USING&lt;/code&gt; clause essentially just needs to return a boolean value, and if it returns &lt;code&gt;true&lt;/code&gt;, then access is granted.&lt;/p&gt;

&lt;p&gt;But ok, that's a mostly pointless example, I just wanted to throw it in in case it helps to clarify the syntax.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example 2: Restricting access to a table based on user ID
&lt;/h4&gt;

&lt;p&gt;Suppose you have a table named &lt;code&gt;tasks&lt;/code&gt; in your database, and you want to implement a security policy that allows users to access only their assigned tasks. You could do this by creating a policy that restricts access to rows in the &lt;code&gt;tasks&lt;/code&gt; table based on the user's ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;task_assignment_policy&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;assigned_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user_id&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy would allow users to access only their assigned tasks, and would prevent them from accessing tasks assigned to other users.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: In the above example, &lt;code&gt;assigned_to&lt;/code&gt; would be a column in the &lt;code&gt;tasks&lt;/code&gt; table, and &lt;code&gt;current_user_id()&lt;/code&gt; would be a function you define that returns the ID of the currently logged-in user, which means it would of course need to be tied to the user's session.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Example 3: Restricting access to a table based on logged in status
&lt;/h4&gt;

&lt;p&gt;Suppose you have an e-learning site, with course content in the &lt;code&gt;course_content&lt;/code&gt; table. You have it setup so that anonymous (non-logged in) users can view the course selection (in a separate table), but they can't view the actual course content. You could do this by creating a policy that restricts access to rows in the &lt;code&gt;courses&lt;/code&gt; table based on the user's logged-in status, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;course_visibility_policy&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;courses&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_logged_in&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So in this simple example, no comparison is being made. We're just checking to see if the user is logged in or not. If they are, then they can access the course content. If they're not logged in, then they can't. Super simple!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: In the above example, &lt;code&gt;is_logged_in()&lt;/code&gt; would be a function you define that returns true if the user is logged in, and false if they are not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When should you use PostgreSQL Row-Level Security?
&lt;/h2&gt;

&lt;p&gt;This question is the main one I was asking myself when I was looking into Supabase. I mean, I understood the idea behind row-level security, but I couldn't figure out &lt;strong&gt;&lt;em&gt;why&lt;/em&gt;&lt;/strong&gt; I would actually need to use it.&lt;/p&gt;

&lt;p&gt;This is because in the app I was going to be building, there would be a backend REST API responsible for handling all the security and data access. So while RLS &lt;em&gt;could&lt;/em&gt; be used to restrict access to data in the database, the actual restrictions were going to be in the REST API.&lt;/p&gt;

&lt;p&gt;It was then that it finally clicked for me, I think thanks to some reddit thread, that &lt;strong&gt;RLS is useful, a necessity really, when you're building a frontend-only app (no backend) that accesses the database directly&lt;/strong&gt;. In that case, you don't have the option of implementing access restrictions in a backend API, so you need to do it &lt;strong&gt;via the database directly&lt;/strong&gt;. And that's where RLS comes in and can be super powerful! 💡😄&lt;/p&gt;

&lt;p&gt;Now of course there are other use cases for RLS, but this is the one that finally made it click for me, and made me realize why it's such a big deal.&lt;/p&gt;

&lt;h2&gt;
  
  
  In a nutshell
&lt;/h2&gt;

&lt;p&gt;So basically, &lt;strong&gt;if you're building a frontend-only app&lt;/strong&gt;, and you're using PostgreSQL as your database (whether through Supabase or not), then you should definitely be using RLS to restrict access to your data.&lt;/p&gt;

&lt;p&gt;On the other hand, if you're building a full-stack app and have a backend of some sort, you can still use RLS, but it's not as big of a deal, and you can probably just implement the same restrictions in your backend API.&lt;/p&gt;




&lt;p&gt;That's it for this post!&lt;/p&gt;

&lt;p&gt;If you were as confused with the purpose of PostgreSQL row-level security as I initially was, I hope it helped to clarify things for you, and why you might want to implement it in your own app.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Build a Weather Widget Using Next.js</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Sat, 27 Jan 2024 14:30:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/build-a-weather-widget-using-nextjs-49m</link>
      <guid>https://forem.com/mlaposta/build-a-weather-widget-using-nextjs-49m</guid>
      <description>&lt;p&gt;In this Next.js tutorial, we'll learn how to create a simple weather widget using Next.js, React, and OpenWeatherMap.&lt;/p&gt;

&lt;p&gt;I'll be keeping things simple, so styling will be done using plain old CSS, and the weather API calls will use the built-in fetch API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can download the final code for this tutorial from &lt;a href="https://github.com/mlaposta/nextjs-weather-widget"&gt;my GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why use Next.js?
&lt;/h3&gt;

&lt;p&gt;This widget is easily doable using basic React, and for the most part, the code &lt;strong&gt;&lt;em&gt;will&lt;/em&gt;&lt;/strong&gt; be just basic React. However, we'll need a way to allow backend-only access to our OpenWeatherMap API key (for security purposes) and make the API calls, and Next.js provides us with an integrated way to do this by utilizing the &lt;code&gt;pages/api&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Of course, alternatively, if you were using Node.js or another backend (Java, PHP, Python, Ruby, etc.), you would just place all of your API calls there instead of using the backend we'll be creating. That however is outside the scope of this tutorial, and so we'll be sticking with a full Next JS build for this one.&lt;/p&gt;

&lt;p&gt;Alright, let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap the Project (optional)
&lt;/h2&gt;

&lt;p&gt;If you're using an existing project, then you've already bootstrapped your project, and can skip ahead to the &lt;strong&gt;Setup your OpenWeatherMap API Key&lt;/strong&gt; section below.&lt;/p&gt;

&lt;p&gt;Otherwise, the first step will be to set up the project boilerplate.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the Project
&lt;/h3&gt;

&lt;p&gt;From the CLI, using yarn, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create next-app weather-widget &lt;span class="nt"&gt;--example&lt;/span&gt; with-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you're using npx, you can use the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app weather-widget &lt;span class="nt"&gt;--example&lt;/span&gt; with-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, when prompted, use the following settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✔ Would you like to use TypeScript? … &lt;span class="o"&gt;[&lt;/span&gt;Yes]
✔ Would you like to use ESLint? … &lt;span class="o"&gt;[&lt;/span&gt;Yes]
✔ Would you like to use Tailwind CSS? … &lt;span class="o"&gt;[&lt;/span&gt;No]
✔ Would you like to use &lt;span class="sb"&gt;`&lt;/span&gt;src/&lt;span class="sb"&gt;`&lt;/span&gt; directory? … &lt;span class="o"&gt;[&lt;/span&gt;No]
✔ Would you like to use App Router? &lt;span class="o"&gt;(&lt;/span&gt;recommended&lt;span class="o"&gt;)&lt;/span&gt; … &lt;span class="o"&gt;[&lt;/span&gt;No]
✔ Would you like to customize the default import &lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;@/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;? … &lt;span class="o"&gt;[&lt;/span&gt;No]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you're using Yarn's PnP (no node_modules folder) along with VS Code as your IDE and are seeing "Cannot find module ..." errors in your TS files after the install, &lt;a href="https://wheresbaldo.dev/tech/vscode/fix-cannot-find-module-typescript-errors-in-vs-code"&gt;see my post here on how to fix those errors&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Delete Unnecessary Files
&lt;/h3&gt;

&lt;p&gt;Delete the following files (but not the folders):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public/*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;styles/*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pages/api/*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Delete Unnecessary Code
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;pages/_app.tsx&lt;/code&gt;, delete the following import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/_app.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../styles/globals.css&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;h2&gt;
  
  
  Setup your OpenWeatherMap API Key
&lt;/h2&gt;

&lt;p&gt;OpenWeatherMap offers a free API tier, which allows for up to 60 calls per minute, and max 1,000,000 calls per month. So we've got plenty for the purposes of this tutorial.&lt;/p&gt;

&lt;p&gt;To get your free key, go to &lt;a href="https://openweathermap.org/"&gt;openweathermap.org&lt;/a&gt; and create an account.&lt;/p&gt;

&lt;p&gt;Once you've created your account:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://home.openweathermap.org/api_keys"&gt;API Keys&lt;/a&gt; page and copy your API key.&lt;/li&gt;
&lt;li&gt;Create a new file in your weather widget project root folder called &lt;code&gt;.env.local&lt;/code&gt; and add the following code, replacing &lt;code&gt;[YOUR_API_KEY_HERE]&lt;/code&gt; with the API key you obtained from OpenWeatherMap:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;# .env.local&lt;/span&gt;

   &lt;span class="nv"&gt;OPENWEATHERMAP_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;YOUR_API_KEY_HERE]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the Weather Data API
&lt;/h2&gt;

&lt;p&gt;The weather API is where we need Next.js to come in and help us out.&lt;/p&gt;

&lt;p&gt;We'll be creating a basic API that will allow us to make calls to OpenWeatherMap from the frontend, without exposing our API key, by routing those calls through our backend (&lt;code&gt;.js&lt;/code&gt; or &lt;code&gt;.ts&lt;/code&gt; files in the &lt;code&gt;pages/api&lt;/code&gt; folder).&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;pages/api&lt;/code&gt; folder, create a file called &lt;code&gt;weather.ts&lt;/code&gt;, and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/weather.ts&lt;/span&gt;

&lt;span class="c1"&gt;// import the Next.js request and response types&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextApiRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextApiResponse&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="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// import the OpenWeatherMap API key from the .env.local file&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&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;OPENWEATHERMAP_API_KEY&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;apiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.openweathermap.org/data/2.5/weather&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="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;NextApiRequest&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;NextApiResponse&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;// extract the query parameters from the request object&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;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lon&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// if the API key isn't found in the .env.local file,&lt;/span&gt;
  &lt;span class="c1"&gt;// return a 500 error to the frontend&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;apiKey&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="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&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="na"&gt;error&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 key not found.&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// if either the city name (q), or coordinates (lat and lon) aren't&lt;/span&gt;
  &lt;span class="c1"&gt;// provided, return a 400 error to the frontend&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;q&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;lon&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="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please provide either city or coordinates.&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// because this is running server-side, we're wrapping the API call&lt;/span&gt;
  &lt;span class="c1"&gt;// in a try / catch block to catch any errors that may occur,&lt;/span&gt;
  &lt;span class="c1"&gt;// and if so, return a 500 error to the frontend&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;q&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="s2"&gt;`lat=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;lon=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lon&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&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;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;appid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;units=metric`&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;await&lt;/span&gt; &lt;span class="nx"&gt;response&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;res&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server error while trying to fetch weather data&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;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;Just like Node.js, Next.js gives us access to the &lt;code&gt;process.env&lt;/code&gt; object, which allows us to access environment variables, like our &lt;code&gt;OPENWEATHERMAP_API_KEY&lt;/code&gt; constant.&lt;/p&gt;

&lt;p&gt;After a few basic checks (check that the &lt;code&gt;query&lt;/code&gt; parameter was passed with either city name &lt;code&gt;q&lt;/code&gt; or coords &lt;code&gt;lat&lt;/code&gt; and &lt;code&gt;lon&lt;/code&gt;), we then use the &lt;code&gt;fetch&lt;/code&gt; function to make the API call to OpenWeatherMap, and return the data to the frontend via the &lt;strong&gt;&lt;em&gt;res&lt;/em&gt;&lt;/strong&gt;ponse method.&lt;/p&gt;

&lt;p&gt;If the call was successful, we return the data to the frontend with an HTTP 200 status code, otherwise we return an error message with the generic HTTP  server error code 500.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the Widget Styling
&lt;/h2&gt;

&lt;p&gt;Next up, we'll define the styling for our widget.&lt;/p&gt;

&lt;p&gt;In a project this small, we could just use a regular CSS file, which would make the CSS classes global.&lt;/p&gt;

&lt;p&gt;I want to scope the CSS to the widget only though, which will avoid possible naming conflicts with other CSS files that could occur in larger projects.&lt;/p&gt;

&lt;p&gt;So in the &lt;code&gt;styles&lt;/code&gt; folder, create a CSS module file called &lt;code&gt;weather.module.css&lt;/code&gt;, and add the following CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* styles/weather.module.css */&lt;/span&gt;

&lt;span class="nc"&gt;.weatherWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.currentWeather&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.currentWeather&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.feelsLike&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.9rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;italic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.weather&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&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;h2&gt;
  
  
  Create the Weather Widget Component
&lt;/h2&gt;

&lt;p&gt;Now it's time to create the actual widget component.&lt;/p&gt;

&lt;p&gt;In your project root folder, create a new folder called &lt;code&gt;components&lt;/code&gt;, and then inside that folder, either using bash or your code editor, create a file called &lt;code&gt;weatherWidget.tsx&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;weatherWidget.tsx&lt;/code&gt;, add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/weatherWidget.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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="s1"&gt;react&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;styles&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;../styles/widget.module.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WeatherWidgetProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;city&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="nl"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WeatherData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&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="nl"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;feels_like&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;icon&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="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;WeatherWidget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WeatherWidgetProps&lt;/span&gt;&lt;span class="o"&gt;&amp;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;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;coordinates&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setWeatherData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WeatherData&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&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;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;coordinates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`lat=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;lon=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please provide either city or coordinates.&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="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;response&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="s2"&gt;`/api/weather?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WeatherData&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;response&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="nf"&gt;setWeatherData&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error fetching weather data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="nf"&gt;fetchData&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;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;coordinates&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weatherWidget&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading weather ...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentWeather&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;
              &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`https://openweathermap.org/img/wn/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@2x.png`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;°C&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;feelsLike&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Feels like: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;feels_like&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;°C
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;WeatherWidget&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, we're using the &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; hooks to fetch the weather data from our API, and then display it in the widget.&lt;/p&gt;

&lt;p&gt;We're using &lt;code&gt;useEffect&lt;/code&gt; so that the API call can be made if / when the &lt;code&gt;city&lt;/code&gt; or &lt;code&gt;coordinates&lt;/code&gt; props change. So if this was in an app with a select box of cities, the widget would update automatically when the user selects a new city.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I've left out proper error handling for the weather API call, opting to just log the error to the console for the sake of this tutorial.&lt;/p&gt;

&lt;p&gt;In a real app, you'd want to handle any errors more gracefully, perhaps by displaying a message to the user, and probably by using a state variable to track the error, and then displaying the error message in the widget.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Update the Index Page
&lt;/h2&gt;

&lt;p&gt;Finally, we'll update the &lt;code&gt;index.tsx&lt;/code&gt; page to use our new widget.&lt;/p&gt;

&lt;p&gt;Simply replace the contents of the original boilerplate in &lt;code&gt;pages/index.tsx&lt;/code&gt; with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/index.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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;react&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;WeatherWidget&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;../components/weatherWidget&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;Home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"App"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Example using city name */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WeatherWidget&lt;/span&gt; &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Montreal"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Example using coordinates */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* &amp;lt;WeatherWidget coordinates={{ lon: -73.5878, lat: 45.5088 }} /&amp;gt; */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, I'm using "Montreal" as the city name, but you can of course replace that with any city name you want.&lt;/p&gt;

&lt;p&gt;If you have trouble getting the weather for a particular city, you can comment out that line, and uncomment the second example which uses the coordinates for that city instead.&lt;/p&gt;




&lt;p&gt;And that's it for the setup and tutorial!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Result
&lt;/h2&gt;

&lt;p&gt;Now, if you run &lt;code&gt;yarn dev&lt;/code&gt; (or &lt;code&gt;npm run dev&lt;/code&gt;), you should get something like the following widget showing in your browser:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Additional Thoughts
&lt;/h2&gt;

&lt;p&gt;The OpenWeatherMap API is pretty robust, and there are other &lt;em&gt;paid&lt;/em&gt; API endpoints that you can access, such as 4-day, 8-day, and 16-day forecasts, among other things.&lt;/p&gt;

&lt;p&gt;Because I'm using the free version of the API in this post however, the access is limited to current weather and a 5-day forecast.&lt;/p&gt;

&lt;p&gt;For further info on the free "current weather" API we're using in this tutorial, refer to the &lt;a href="https://openweathermap.org/current"&gt;Current weather data API documentation&lt;/a&gt; for all the available data points.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>5 Ways to Redirect URLs in Next.JS</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Fri, 19 Jan 2024 14:30:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/5-ways-to-redirect-urls-in-nextjs-1k4n</link>
      <guid>https://forem.com/mlaposta/5-ways-to-redirect-urls-in-nextjs-1k4n</guid>
      <description>&lt;p&gt;Chances are at some point in the development of a web app or website, you've changed the path to a URL for whatever reason. Maybe you've changed the name of a page, or you've changed the structure of your site. Whatever the reason, you'll need to redirect the old URL to the new one.&lt;/p&gt;

&lt;p&gt;In this post we'll explore 5 different methods you can use to redirect URLs in Next.JS, using Pages router, each with their own pros and cons.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that for the purposes of this post, I'm assuming permanent redirects. If you need temporary redirects, you can simply change the &lt;code&gt;permanent&lt;/code&gt; property to &lt;code&gt;false&lt;/code&gt; in the first 3 code examples below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Method 1: Using next.config.js
&lt;/h2&gt;

&lt;p&gt;The first method I'll outline is probably the simplest, and when it works, the fastest.&lt;/p&gt;

&lt;p&gt;I say "when it works" because it's not necessarily supported by all hosts. For example, Vercel of course supports it, but &lt;a href="https://github.com/netlify/next-runtime/issues/2359"&gt;Netlify doesn't currently appear to&lt;/a&gt;. As for other hosts, you'd need to verify with them, but I'd imagine it varies.&lt;/p&gt;

&lt;p&gt;To use this method:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;If you haven't already, create a &lt;code&gt;next.config.js&lt;/code&gt; file in the root of your project (at the same level as your &lt;code&gt;package.json&lt;/code&gt; file).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the following code to the file:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// next.config.js&lt;/span&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="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="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;/old-url-1&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;/new-url-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="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;/old-url-2&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;/new-url-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;etc&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;That's it! Now, when a user visits &lt;code&gt;/old-url-1&lt;/code&gt;, they'll be redirected to &lt;code&gt;/new-url-1&lt;/code&gt;. The same goes for &lt;code&gt;/old-url-2&lt;/code&gt; and &lt;code&gt;/new-url-2&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;permanent&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; will make the redirect permanent, and cause a 308 HTTP status code to be returned, which is what you want in most cases. If you need a temporary redirect, you can set &lt;code&gt;permanent&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;, which returns a 307 HTTP status code instead.&lt;/p&gt;

&lt;p&gt;This method is super simple, and allows for a lot of options including dynamic path matching and even regex matching.&lt;/p&gt;

&lt;p&gt;For full details, check out the &lt;a href="https://nextjs.org/docs/pages/api-reference/next-config-js/redirects"&gt;official Next.JS redirects documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Using getStaticProps
&lt;/h2&gt;

&lt;p&gt;Now, I'm including this purely because it's listed in the &lt;a href="https://nextjs.org/docs/pages/api-reference/next-config-js/redirects"&gt;official Next.JS documentation&lt;/a&gt;, &lt;strong&gt;&lt;em&gt;but it doesn't actually seem to work!!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well, that's not entirely true ... it works amazingly well in development, but doesn't seem to work at all in production. And call me crazy, but isn't production exactly where you would need it to work? 🤔🤨&lt;/p&gt;

&lt;p&gt;If you use the &lt;code&gt;redirect&lt;/code&gt; key in getStaticProps in production, you'll likely get the following error during build time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error: 'redirect' can not be returned from getStaticProps during prerendering&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;According to user &lt;strong&gt;timfuhrmann&lt;/strong&gt; in &lt;a href="https://github.com/vercel/next.js/discussions/11346#discussioncomment-3612448"&gt;this github thread&lt;/a&gt;, this style of redirect only works if &lt;code&gt;fallback&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;'blocking'&lt;/code&gt;, but I couldn't get it to work any way I tried. I guess I'm doing something wrong, but the docs make no mention of how to correctly do it, and I couldn't find any other info on it.&lt;/p&gt;

&lt;p&gt;So, I'm not entirely sure what the purpose of having it is, and why it's listed in the official documentation. I'm including it here for completeness, and so that in the event you run into this error like I did, you understand it's because &lt;strong&gt;it doesn't seem to work in production&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you know what I'm doing wrong, &lt;strong&gt;and what the docs obviously don't mention&lt;/strong&gt;, please let me know in the comments and I'll be sure to update this post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The code is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/[slug].js | pages/[...slug].js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;redirectUrl&lt;/span&gt; &lt;span class="o"&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;if &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="nx"&gt;slug&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;old-url-1&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;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new-url-1&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="k"&gt;if &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="nx"&gt;slug&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;old-url-2&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;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new-url-2&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;return&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="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="nx"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;,&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticPaths&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="na"&gt;paths&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="na"&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;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;old-url-1&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;span class="na"&gt;fallback&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; if you're using dynamic paths, you'll likely need to export both &lt;code&gt;getStaticProps&lt;/code&gt; and &lt;code&gt;getStaticPaths&lt;/code&gt;, whereas for a direct path match, you'll only need &lt;code&gt;getStaticProps&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now when a user visits &lt;code&gt;/old-url-1&lt;/code&gt;, they'll be redirected to &lt;code&gt;/new-url-1&lt;/code&gt;, but again, &lt;strong&gt;this only seems to work work in development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Like &lt;strong&gt;Method 1&lt;/strong&gt; however, this method also allows for dynamic path matching and regex matching, so if you only need redirects in dev, this could be a solution. That said, I'd recommend using &lt;strong&gt;Method 1&lt;/strong&gt; instead, as it's much simpler and more reliable when supported by your host.&lt;/p&gt;

&lt;p&gt;For more info on this method, you can check out the &lt;a href="https://nextjs.org/docs/pages/api-reference/functions/get-static-props#redirect"&gt;official Next.JS getStaticProps documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: Using getServerSideProps
&lt;/h2&gt;

&lt;p&gt;Unlike &lt;code&gt;getStaticProps&lt;/code&gt;, &lt;code&gt;getServerSideProps&lt;/code&gt; &lt;strong&gt;&lt;em&gt;does&lt;/em&gt;&lt;/strong&gt; work in production. So if you need redirects in production, this is a good option, albeit a slower one compared to &lt;strong&gt;Method 1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This method uses server side rendering, so while it's not as fast as a static redirect, it's still being done server-side, which can be a plus.&lt;/p&gt;

&lt;p&gt;You also don't need to worry about &lt;code&gt;fallback&lt;/code&gt; or &lt;code&gt;revalidate&lt;/code&gt; options, as they're not needed here.&lt;/p&gt;

&lt;p&gt;The code is quite 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="c1"&gt;// pages/[slug].js | pages/[...slug].js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getServerSideProps&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;redirectUrl&lt;/span&gt; &lt;span class="o"&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;if &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="nx"&gt;slug&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;old-url-1&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;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new-url-1&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="k"&gt;if &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="nx"&gt;slug&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;old-url-2&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;redirectUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new-url-2&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;return&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="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="nx"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;,&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;Again, this method also allows for dynamic path matching, which you would accomplish by using the &lt;code&gt;params&lt;/code&gt; object, and by placing the code in a file with a dynamic route name, like &lt;code&gt;[id].js&lt;/code&gt;, &lt;code&gt;[slug].js&lt;/code&gt; or &lt;code&gt;[...slug].js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 4: Using a middleware.ts or .js file
&lt;/h2&gt;

&lt;p&gt;This method is quite a bit more complicated, but it's also a lot more flexible. Using middleware, you can redirect to any other URL, including external URLs.&lt;/p&gt;

&lt;p&gt;You can also use conditional logic, cookies, and you can set the response headers as well.&lt;/p&gt;

&lt;p&gt;To use this method, you'll need to create a &lt;code&gt;middleware.ts&lt;/code&gt; or &lt;code&gt;middleware.js&lt;/code&gt; file in the root of your project (at the same level as your &lt;code&gt;package.json&lt;/code&gt; file).&lt;/p&gt;

&lt;p&gt;Add the following code to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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="s1"&gt;next/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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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="s1"&gt;next/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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&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;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/old-url&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;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new-url&lt;/span&gt;&lt;span class="dl"&gt;'&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="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;Or, alternatively, using path matching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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="s1"&gt;next/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="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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="s1"&gt;next/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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&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;NextRequest&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new-url&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/old-url/:path*&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In my test &lt;code&gt;middleware.js&lt;/code&gt; file, VS Code is showing an error with the NextResponse import, but it's working fine. Seems to be something not updated as it wants me to keep the file in the pages directory, which is no longer required / supported.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0r5s7j0jxetq2j6q2r7v.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0r5s7j0jxetq2j6q2r7v.jpg" alt="VS Code error with NextResponse import" width="580" height="315"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An important caveat with this method though, per the Next.JS docs, is that it currently only supports the Edge runtime, and not the Node.js runtime.&lt;/p&gt;

&lt;p&gt;Refer to the &lt;a href="https://nextjs.org/docs/pages/building-your-application/routing/middleware"&gt;official Next.JS middleware documentation&lt;/a&gt; for the full list of features and options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 5: Using useEffect
&lt;/h2&gt;

&lt;p&gt;And finally, the last method I'll outline is using &lt;code&gt;useEffect&lt;/code&gt; in a React component. This method is probably the most flexible, but it's also not really a true redirect.&lt;/p&gt;

&lt;p&gt;This method would be somewhat similar to the old school method of using a meta refresh tag in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your HTML document, but instead of using a meta tag, we'll use React's &lt;code&gt;useEffect&lt;/code&gt; along with Next's &lt;code&gt;useRouter&lt;/code&gt; to redirect the user to the new URL.&lt;/p&gt;

&lt;p&gt;Like the middleware method, we can redirect to an external URL, as well as use conditional logic, cookies, etc.&lt;/p&gt;

&lt;p&gt;The code is as follows:&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;useEffect&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="s1"&gt;react&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&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="s1"&gt;next/router&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;OldPage&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/new-url&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;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&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;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Old&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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;Well, those were the 5 different methods I wanted to outline in this post. Note that if you're using &lt;strong&gt;App Router&lt;/strong&gt; instead of &lt;strong&gt;Pages Router&lt;/strong&gt;, you might have different options / methods available to you. &lt;/p&gt;

&lt;p&gt;Hope you learned something new in this post, and that it helped if you got stuck somewhere in your own code. 😀&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fix "Cannot find module ..." TypeScript errors in VS Code</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Sat, 13 Jan 2024 15:00:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/fix-cannot-find-module-typescript-errors-in-vs-code-1mjo</link>
      <guid>https://forem.com/mlaposta/fix-cannot-find-module-typescript-errors-in-vs-code-1mjo</guid>
      <description>&lt;p&gt;As a developer, I'm all too familiar with things not working as planned, and bugs popping up anywhere and everywhere. I'm also all too familiar with the frustration that comes with it.&lt;/p&gt;

&lt;p&gt;But finding / creating bugs while coding is just par for the course, so it's nothing new. It's just part of the job. And I've learned to accept it.&lt;/p&gt;

&lt;p&gt;What really frustrates me though, is when I bootstrap a new project, and there are errors right from the get-go! 😡&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;Enter this new project I started. I'm using NextJS and Yarn 3, and I bootstrapped it using Next's &lt;code&gt;create-next-app&lt;/code&gt; with Yarn, as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create next-app

✔ Would you like to use TypeScript? … &lt;span class="o"&gt;[&lt;/span&gt;Yes]
✔ Would you like to use ESLint? … &lt;span class="o"&gt;[&lt;/span&gt;Yes]
✔ Would you like to use Tailwind CSS? … &lt;span class="o"&gt;[&lt;/span&gt;Yes]
✔ Would you like to use &lt;span class="sb"&gt;`&lt;/span&gt;src/&lt;span class="sb"&gt;`&lt;/span&gt; directory? … &lt;span class="o"&gt;[&lt;/span&gt;No]
✔ Would you like to use App Router? &lt;span class="o"&gt;(&lt;/span&gt;recommended&lt;span class="o"&gt;)&lt;/span&gt; … &lt;span class="o"&gt;[&lt;/span&gt;No]
✔ Would you like to customize the default import &lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;@/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;? … &lt;span class="o"&gt;[&lt;/span&gt;No]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty straight forward and simple no?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript ... check&lt;/li&gt;
&lt;li&gt;ESLint ... check&lt;/li&gt;
&lt;li&gt;Tailwind CSS ... check&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Certainly, nothing really out of the ordinary here. That said, I am using Yarn 3 (Yarn Berry), which uses it's "Zero Installs" plug and play system, and means that there's no &lt;code&gt;node_modules&lt;/code&gt; folder. As I've experienced before, the missing &lt;code&gt;node_modules&lt;/code&gt; folder can cause issues sometimes.&lt;/p&gt;

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

&lt;p&gt;So I opened Visual Studio Code and checked out the &lt;code&gt;tailwind.config.js&lt;/code&gt; file, and I was greeted with this succulent error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cannot find module 'tailwindcss' or its corresponding type declarations&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsinoost09i3p3q2r3r7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsinoost09i3p3q2r3r7.jpg" alt="Cannot find module error" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I thought it seemed strange, because I selected Tailwind in the &lt;code&gt;create-next-app&lt;/code&gt; options. Create-next-app surely would have installed what was needed right? So I checked my &lt;code&gt;package.json&lt;/code&gt; and sure enough, the &lt;strong&gt;&lt;em&gt;tailwindcss&lt;/em&gt;&lt;/strong&gt; dependency was there, along with the React dependencies and types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14.0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^18"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"react-dom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^18"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@types/node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@types/react"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^18"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@types/react-dom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^18"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"autoprefixer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^10.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eslint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eslint-config-next"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14.0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postcss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.3.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^5"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In any case, I decided to check the &lt;code&gt;index.tsx&lt;/code&gt; file, and it was even worse, spitting out '&lt;strong&gt;... Cannot find module ...&lt;/strong&gt;' and '&lt;strong&gt;... no interface exists ...&lt;/strong&gt;' errors all over the file! 😱&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cannot find module 'next/image' or its corresponding type declarations.&lt;/p&gt;

&lt;p&gt;JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu31p6i16tsgyhkysy57.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu31p6i16tsgyhkysy57.jpg" alt="Cannot find module error" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  First Attempts to Fix the Issue
&lt;/h2&gt;

&lt;p&gt;Being relatively new to TypeScript and working with it, I vaguely remembered having issues with TypeScript imports before in Visual Studio Code, but thought I had taken care of the issue globally previously. Apparently not. 🤨&lt;/p&gt;

&lt;h3&gt;
  
  
  VS Code SDK &amp;amp; TypeScript Version
&lt;/h3&gt;

&lt;p&gt;So I ran the steps I thought I did &lt;a href="https://wheresbaldo.dev/tech/monorepos/update-turborepo-to-use-yarn-berry#update-yarn-berry"&gt;last time this happened&lt;/a&gt;, which was to install the &lt;strong&gt;VS Code SDK&lt;/strong&gt; package from the CLI ...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn dlx @yarnpkg/sdks vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then select the workspace TypeScript version, and finally restart the TypeScript server.&lt;/p&gt;

&lt;p&gt;But ... it didn't work this time. I was still getting the same damn errors. 😒&lt;/p&gt;

&lt;p&gt;So, I rolled up my sleeves, prayed to the Google-fu gods, and started searching for a solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opting Out of Yarn PnP
&lt;/h3&gt;

&lt;p&gt;I found a few posts on Stack Overflow, but most of the "solutions" were to simply opt-out of Yarn PnP, which was exactly what I wanted to avoid. &lt;a href="https://stackoverflow.com/questions/69238794/cannot-find-module-next-or-its-corresponding-type-declarations"&gt;This post on Stack Overflow&lt;/a&gt; for example had several people talking about setting the Yarn version to &lt;strong&gt;&lt;em&gt;classic&lt;/em&gt;&lt;/strong&gt; or adding the &lt;strong&gt;nodeLinker: node-modules&lt;/strong&gt; option in &lt;code&gt;.yarnrc.yml&lt;/code&gt; to downgrade Yarn and create the &lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Uhh, no thanks... I'm not going back to the old &lt;code&gt;node_modules&lt;/code&gt; mess!&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing the Dependencies with npm
&lt;/h3&gt;

&lt;p&gt;Others in the same thread suggested to use npm to install the dependencies, and then re-run Yarn. But, that's completely pointless, and ... then why use Yarn at all in the first place? 🤔&lt;/p&gt;

&lt;h3&gt;
  
  
  Explicitly Installing Types
&lt;/h3&gt;

&lt;p&gt;I figured I might as well try installing the types explicitly, despite assuming they'd already be there (not in &lt;code&gt;package.json&lt;/code&gt;, but in the main code), so I ran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; @types/next @types/tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that also did nothing as I figured, and it should also be noted that these packages have been deprecated per npmjs.org:&lt;/p&gt;

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

&lt;p&gt;So, time to put on my thinking cap and figure this out the right way...&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Solution
&lt;/h2&gt;

&lt;p&gt;I went back over what I did last time, and noticed I had also explicitly set the Yarn version to &lt;strong&gt;&lt;em&gt;berry&lt;/em&gt;&lt;/strong&gt; last time, which I hadn't done this time.&lt;/p&gt;

&lt;p&gt;So I tried that again, and thankfully, finally, it got things working! 🎉&lt;/p&gt;

&lt;p&gt;I had originally mistakenly assumed that because I bootstrapped the project with Yarn Berry, that it would have already been set to &lt;strong&gt;&lt;em&gt;berry&lt;/em&gt;&lt;/strong&gt; by default, but it seems that isn't the case.&lt;/p&gt;

&lt;p&gt;Explicitly setting it to &lt;strong&gt;&lt;em&gt;berry&lt;/em&gt;&lt;/strong&gt; added a &lt;strong&gt;&lt;em&gt;"packageManager": "&lt;a href="mailto:yarn@4.0.1"&gt;yarn@4.0.1&lt;/a&gt;"&lt;/em&gt;&lt;/strong&gt; to &lt;code&gt;package.json&lt;/code&gt;, and then running yarn again after caused Yarn to pull in any updated dependencies, including those for TypeScript.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step-by-Step Instructions
&lt;/h4&gt;

&lt;p&gt;So here's what I had to do to fix the issue while keeping Yarn PnP, step-by-step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;From the CLI:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn &lt;span class="nb"&gt;set &lt;/span&gt;version berry &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; yarn
yarn dlx @yarnpkg/sdks vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then in Visual Studio Code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set the TypeScript version to the workspace version:

&lt;ol&gt;
&lt;li&gt;Open a TypeScript file, and open the command palette:

&lt;ul&gt;
&lt;li&gt;on a Mac, press ⌘ + Shift + p
&lt;/li&gt;
&lt;li&gt;on a PC, press Ctrl + Shift + p
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;&lt;em&gt;Select TypeScript Version&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;And then select &lt;strong&gt;&lt;em&gt;Use Workspace Version&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;Restart the TypeScript server:

&lt;ol&gt;
&lt;li&gt;Go back into the command palette:

&lt;ul&gt;
&lt;li&gt;on a Mac, press ⌘ + Shift + p
&lt;/li&gt;
&lt;li&gt;on a PC, press Ctrl + Shift + p
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;&lt;em&gt;TypeScript: Restart TS server&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;a href="https://yarnpkg.com/getting-started/editor-sdks"&gt;Yarn also recommends installing the &lt;em&gt;ZipFS&lt;/em&gt; extension&lt;/a&gt; for VS Code, so you may need to get that as well if you don't already have it installed.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;And that's it - super simple!&lt;/p&gt;

&lt;p&gt;The errors disappeared in all of the files, and I was able to get back to work without having to resort to downgrading Yarn and opting out of PnP.&lt;/p&gt;

&lt;p&gt;So if you're experiencing any issues like I was, whether:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cannot find module '...' or its corresponding type declarations&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;... and you don't want to revert back to the old &lt;code&gt;node_modules&lt;/code&gt; mess, then try the steps above to see if they fix your issue(s).&lt;/p&gt;

&lt;p&gt;Did this help you out? Let me know in the comments below!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>vscode</category>
      <category>howtofix</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fix "Unknown at rule @tailwind" errors in VS Code</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Sat, 06 Jan 2024 14:00:00 +0000</pubDate>
      <link>https://forem.com/mlaposta/fix-unknown-at-rule-tailwind-errors-in-vs-code-208f</link>
      <guid>https://forem.com/mlaposta/fix-unknown-at-rule-tailwind-errors-in-vs-code-208f</guid>
      <description>&lt;p&gt;TailwindCSS is something I've only just come into recently, while working on an existing project started by someone else. So I'm a complete beginner when it comes to Tailwind.&lt;/p&gt;

&lt;p&gt;Initially I couldn't really see the point of Tailwind. I mean it really just looked to me like writing inline-style rules, but in the &lt;code&gt;class&lt;/code&gt; attribute instead of the &lt;code&gt;style&lt;/code&gt; attribute. But since the project was already using it, I had to at least understand the basics.&lt;/p&gt;

&lt;p&gt;Well, after using it for a while, and reading through some of the Tailwind docs, I can now see the benefits, and I'm actually starting to really like it. So when I started bootstrapping a new project a few weeks ago, I thought "Hey, why not use Tailwind?"!&lt;/p&gt;

&lt;p&gt;So after bootstrapping my app, and since I'm building the app using Next.JS, I started by installing TailwindCSS following the &lt;a href="https://tailwindcss.com/docs/guides/nextjs"&gt;Tailwind docs for a Next JS installation&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;After getting Tailwind installed, I checked out a few of the files, and I noticed an issue that had been bothering me in my last project ... Visual Studio Code's IntelliSense wasn't working for the &lt;code&gt;@tailwind&lt;/code&gt; and &lt;code&gt;@apply&lt;/code&gt; directives in my global styles file! 😡&lt;/p&gt;

&lt;p&gt;So while the directives should have looked something like this, with no errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visual Studio Code was underlining them with the yellow squiggly line, indicating a problem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6me80tk4wzqmo99fwcrn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6me80tk4wzqmo99fwcrn.jpg" alt="Unknown at rule @tailwind" width="640" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Specifically, Visual Studio Code was throwing the errors "&lt;strong&gt;Unknown at rule &lt;a class="mentioned-user" href="https://dev.to/tailwind"&gt;@tailwind&lt;/a&gt;&lt;/strong&gt;" or "&lt;strong&gt;Unknown at rule &lt;a class="mentioned-user" href="https://dev.to/apply"&gt;@apply&lt;/a&gt;&lt;/strong&gt;", depending on which one(s) I was using.&lt;/p&gt;

&lt;p&gt;On the last project, laziness got the better of me and I just ignored it, and it didn't seem to be causing any issues. But this time, starting a new project, I had to do something about it or I knew it would just drive me nuts! 😒&lt;/p&gt;

&lt;p&gt;So after some quick digging, I found a few stackoverflow posts that addressed the issue a few different ways, but this &lt;a href="https://stackoverflow.com/questions/76970920/how-to-make-vscode-recognize-tailwinds-apply"&gt;more recent one&lt;/a&gt; was the most up to date, and to the point.&lt;/p&gt;

&lt;p&gt;However, like the other answers I found, it was missing a crucial step I needed to get it working. So after doing some more digging, and a bit of trial and error, I was finally able to get things working.&lt;/p&gt;

&lt;p&gt;Here's what I did to fix the issue:&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix VS Code's IntelliSense for TailwindCSS
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First off, if you haven't already, start by installing the TailwindCSS package in your project:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using npm&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;tailwindcss

&lt;span class="c"&gt;# Using Yarn&lt;/span&gt;
yarn add tailwindcss
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Or, for a Next.JS project, like I'm doing:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using npm&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; tailwindcss postcss autoprefixer
npx tailwindcss init &lt;span class="nt"&gt;-p&lt;/span&gt;

&lt;span class="c"&gt;# Using Yarn&lt;/span&gt;
yarn add &lt;span class="nt"&gt;-D&lt;/span&gt; tailwindcss postcss autoprefixer
yarn dlx tailwindcss init &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, in your &lt;code&gt;tailwind.config.css&lt;/code&gt;, which was just created with the &lt;code&gt;tailwindcss init -p&lt;/code&gt; command, add the following to the &lt;strong&gt;module.exports&lt;/strong&gt; &lt;code&gt;content&lt;/code&gt; key:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;content:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"./app/**/*.{js,ts,jsx,tsx,mdx}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"./pages/**/*.{js,ts,jsx,tsx,mdx}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"./components/**/*.{js,ts,jsx,tsx,mdx}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Or, if you're using the 'src' folder in your project:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;content:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"./src/**/*.{js,ts,jsx,tsx,mdx}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once that's done, and this was the crucial missing step in the answers I found online:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go into your extensions in Visual Studio Code, and&lt;/li&gt;
&lt;li&gt;Install the &lt;strong&gt;Tailwind CSS IntelliSense&lt;/strong&gt; extension.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3pdajrrrzoz420eo1ys.jpg" alt="VS Code TailwindCSS IntelliSense extension" width="800" height="453"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is what will allow Visual Studio Code to actually recognize the directives.&lt;/p&gt;


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

&lt;p&gt;And then finally, after the TailwindCSS IntelliSense extension is installed, you'll need to add a file association. To do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go into your Visual Studio Code settings:
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz43m39jqc4028nmzofas.jpg" alt="VS Code Settings" width="510" height="316"&gt;

&lt;ul&gt;
&lt;li&gt;On a Mac, you can do this by going to &lt;strong&gt;Code &amp;gt; Settings... &amp;gt; Settings&lt;/strong&gt;, or by using the keyboard shortcut ⌘ + ,.&lt;/li&gt;
&lt;li&gt;On a PC, you can do this by going to &lt;strong&gt;File &amp;gt; Preferences &amp;gt; Settings&lt;/strong&gt;, or by using the keyboard shortcut Ctrl + ,.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Then, in the search bar, type in "files associations", and click on the &lt;strong&gt;Add Item&lt;/strong&gt; button under the 'Files: &lt;strong&gt;Associations&lt;/strong&gt;' section.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3arvf8ya6q1j82hdnah.jpg" alt="VS Code Settings" width="800" height="246"&gt;
&lt;/li&gt;
&lt;li&gt;Under the &lt;strong&gt;Item&lt;/strong&gt; column, add "*.css" (without the quotes), and under the &lt;strong&gt;Value&lt;/strong&gt; column, add "tailwindcss" (again, without the quotes). Click the &lt;strong&gt;Ok&lt;/strong&gt; button to save the changes.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkx5iekdo6cxgf59046lm.jpg" alt="VS Code Settings" width="631" height="198"&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's all there is to it!&lt;/p&gt;

&lt;p&gt;Simple eh? 😁&lt;/p&gt;

&lt;p&gt;Now, when you use the &lt;code&gt;@tailwind&lt;/code&gt; and &lt;code&gt;@apply&lt;/code&gt; directives in your CSS files, Visual Studio Code will no longer throw the "Unknown at rule &lt;a class="mentioned-user" href="https://dev.to/tailwind"&gt;@tailwind&lt;/a&gt;" or "Unknown at rule &lt;a class="mentioned-user" href="https://dev.to/apply"&gt;@apply&lt;/a&gt;" errors, and you'll get the IntelliSense you're used to with CSS!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf4gzffh7p8gs5w7owtd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf4gzffh7p8gs5w7owtd.jpg" alt="Tailwind CSS IntelliSense installed" width="295" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;If you've followed posts online that miss the installation of the TailwindCSS IntelliSense extension, you'll end up with broken IntelliSense for CSS, and your CSS files will look like regular text files without any highlighting:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8124deyfz0k5t3pzeqw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8124deyfz0k5t3pzeqw.jpg" alt="Tailwind CSS IntelliSense installed" width="288" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So if you've followed other instructions online that left you with non-highlighted CSS files and broken IntelliSense, you're just missing the TailwindCSS IntelliSense extension to fix things up!&lt;/p&gt;

&lt;p&gt;Hope this post was able to help if you were in the same boat as me!&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>tailwindcss</category>
      <category>webdev</category>
      <category>howtofix</category>
    </item>
    <item>
      <title>Build a Simple Contact Form with Next.JS and Netlify</title>
      <dc:creator>Michael La Posta</dc:creator>
      <pubDate>Fri, 29 Dec 2023 12:10:25 +0000</pubDate>
      <link>https://forem.com/mlaposta/build-a-simple-contact-form-with-nextjs-and-netlify-2p6o</link>
      <guid>https://forem.com/mlaposta/build-a-simple-contact-form-with-nextjs-and-netlify-2p6o</guid>
      <description>&lt;p&gt;Adding forms to a website can be a simple matter, or a complex one, depending on the requirements and environment the website is running on.&lt;/p&gt;

&lt;p&gt;In the dev and production environments I've worked in professionally, I've simply built-up any forms I needed using React and JavaScript, and then sent the form data to one of my ExpressJS API endpoints, which would either spit the data into a database, or shoot off an email using corporate SMTP servers.&lt;/p&gt;

&lt;p&gt;On the other hand, if you're hosting a site in the cloud, you might not have access to a backend API, or SMTP server, or you might not want to have to deal with the hassle of setting up and maintaining one. That's where something like &lt;a href="https://www.netlify.com"&gt;Netlify&lt;/a&gt;'s serverless form handling can come in really handy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Netlify Forms
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Netlify’s serverless form handling allows you to manage forms without extra API calls or additional JavaScript. Once enabled, the built-in form detection feature allows our build system to automatically parse your HTML at deploy time, so there’s no need for you to make an API call or include extra JavaScript on your site.&lt;br&gt;
&lt;a href="https://docs.netlify.com/forms/setup/"&gt;docs.netlify.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So with Netlify form handling, you can create HTML-only forms, and Netlify will handle the rest for you. Simple as that.&lt;/p&gt;

&lt;p&gt;You do however have the option to use JavaScript, or a library like React, or even a system that will statically render the form for you, like &lt;a href="https://nextjs.org"&gt;Next JS&lt;/a&gt;, which is what I'll be using in this post.&lt;/p&gt;

&lt;p&gt;Now a lot of the examples I found online gave some basic info on how to set things up, but generally using plain old HTML. Even the ones using Next JS, as I was doing, didn't include examples of how to do custom success or error fetching or omitted important details. So I figured I'd write this post to help others out who might be looking for a more complete example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrapping the Next JS App
&lt;/h2&gt;

&lt;p&gt;You can bootstrap your app anyway you like, but for simplicity, I'm using the &lt;code&gt;next-app&lt;/code&gt; template. I'm also calling my project (and folder) &lt;code&gt;nextjs-netlify-form&lt;/code&gt;, but you can call it whatever you like.&lt;/p&gt;

&lt;p&gt;Using yarn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create next-app nextjs-netlify-form
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you're using npx, you can use the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app nextjs-netlify-form
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the project initialized, I used the following settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✔ What is your project named? … nextjs-netlify-form
✔ Would you like to use TypeScript? … No
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use &lt;span class="sb"&gt;`&lt;/span&gt;src/&lt;span class="sb"&gt;`&lt;/span&gt; directory? … No
✔ Would you like to use App Router? &lt;span class="o"&gt;(&lt;/span&gt;recommended&lt;span class="o"&gt;)&lt;/span&gt; … No
✔ Would you like to customize the default import &lt;span class="nb"&gt;alias&lt;/span&gt;? … No
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that's all done, change into the project directory:&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;cd &lt;/span&gt;nextjs-netlify-form
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then open up your code editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the Form
&lt;/h2&gt;

&lt;p&gt;Now, for this example form, I don't really care too much about styling, as the main focus here is on the form handling. I'll use some minimal Tailwind CSS styling for the form, but won't be discussing it here as that's beyond the scope of this post.&lt;/p&gt;

&lt;p&gt;So in your code editor, open the &lt;code&gt;pages/index.js&lt;/code&gt; file, remove everything, and then add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;useState&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;react&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container mx-auto"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl font-bold text-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Contact Form&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Next, open the &lt;code&gt;styles/globals.css&lt;/code&gt; file, and remove the body styling, so it now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--foreground-rgb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--background-start-rgb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;214&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;219&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;220&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--background-end-rgb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--foreground-rgb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--background-start-rgb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--background-end-rgb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&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;Now we'll need to add the three basic contact form elements, which will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So back in the &lt;code&gt;pages/index.js&lt;/code&gt; file, modify it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;useState&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;react&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container mx-auto max-w-lg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl font-bold text-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Contact Form&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col space-y-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-lg font-bold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Name&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border border-gray-300 rounded-md"&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
            &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Name (required)"&lt;/span&gt;
            &lt;span class="na"&gt;required&lt;/span&gt;
          &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-lg font-bold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border border-gray-300 rounded-md"&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
            &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Email address"&lt;/span&gt;
          &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-lg font-bold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Message&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"border border-gray-300 rounded-md"&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;
            &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Message (required)"&lt;/span&gt;
            &lt;span class="na"&gt;required&lt;/span&gt;
          &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Submit
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;blockquote&gt;
&lt;p&gt;Notice in the above code, for the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt; fields, I've added the &lt;code&gt;required&lt;/code&gt; attribute. This will make sure that the user has to fill in those fields before the form can be submitted. I've left the email as optional for a few reasons:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Email validation is a tricky business, and even expert developers disagree on the one true way to validate an email address. There's also the argument that no matter what client-side validation you do, the only real way to test the validity of an email address is to send an email to it, and see if it bounces. And that's way beyond the scope of this post.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The end-user might not want to supply their email address for any number of reasons, especially in this day and age of privacy concerns. They might also just want to drop a simple "Hey, your site sucks and you can't code for sh!t!" message, and not want to be contacted back. So forcing them to supply an email address would be a bad user experience. 🤪&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, if you run the app from the command shell using &lt;code&gt;yarn dev&lt;/code&gt; or &lt;code&gt;npm run dev&lt;/code&gt;, you should see the following in your browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7atnf2hx1oat88kbtdu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7atnf2hx1oat88kbtdu.jpg" alt="Contact form appearance in the browser" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, so we've got a basic form with the fields we need, but it currently doesn't do anything! So we'll now add the Netlify form handling data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Netlify Form Handling Data
&lt;/h2&gt;

&lt;p&gt;Still in the &lt;code&gt;pages/index.js&lt;/code&gt; file, in the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag, we'll add the following attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col space-y-4"&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"contact"&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;
  &lt;span class="na"&gt;data-netlify&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;data-netlify-honeypot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bot-sniffer"&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important things to note here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;name&lt;/code&gt; attribute, which Netlify will use to identify the form.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;action&lt;/code&gt; attribute, which will allow us to remain on the same page after the form is submitted. You can omit the action attribute if you want, but that will cause Netlify to redirect to &lt;a href="https://docs.netlify.com/forms/setup/#success-messages"&gt;their success page&lt;/a&gt; after the form is submitted successfully, which isn't something I want. Note that I've used the single forward-slash ('/'), because in this example, we are running from the root of the site. If you were running from a sub-directory, you'd need to add that route to the action attribute after the '/'.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;data-netlify="true"&lt;/code&gt; attribute, which is the "hook" that tells Netlify to handle the form submission.&lt;/li&gt;
&lt;li&gt;And the &lt;code&gt;data-netlify-honeypot="bot-sniffer"&lt;/code&gt; attribute, which is a "honeypot" field that will help prevent spam submissions. We'll go into this next ...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag we just added, we'll now add a hidden &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; tag, followed by our honeypot field as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col space-y-4"&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"contact"&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;
  &lt;span class="na"&gt;data-netlify&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;data-netlify-honeypot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bot-sniffer"&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"form-name"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"contact"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'hidden'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'bot-sniffer'&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 1st &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; tag is the one that Netlify needs to use to identify the form, without which Netlify's hook won't run. Note that the &lt;code&gt;value&lt;/code&gt; attribute of this &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; tag must match the &lt;code&gt;name&lt;/code&gt; attribute of the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;The 2nd &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; tag is our honeypot field. The honeypot &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;'s &lt;code&gt;name&lt;/code&gt; attribute must match the &lt;code&gt;data-netlify-honeypot&lt;/code&gt; attribute in the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag for the honeypot to work. The &lt;code&gt;hidden&lt;/code&gt; class will hide the field from the user, as we don't want the actual user filling it out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The idea behind the honeypot field is that bots will fill out all the fields in a form, including the hidden ones, whereas humans won't. So if the honeypot field is filled out, we know it's most likely a bot, and Netlify's workflow will automatically flag the submission as spam.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Adding Some JavaScript for Validation and Posting
&lt;/h2&gt;

&lt;p&gt;Now, as the form is currently, we're almost ready to call it a day. But if we want to do some custom error handling, or success handling, we'll need to add some JavaScript to the mix.&lt;/p&gt;

&lt;p&gt;Additionally, while the form will require the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt; fields to have some text in them due to our use of the &lt;code&gt;required&lt;/code&gt; attribute on the inputs, it won't actually check to see if a valid name or message was entered.&lt;/p&gt;

&lt;p&gt;What's a valid name and message you ask? Well, I suppose that's debatable. But for this example, I'll say that a valid name is one that has at least 3 letters, and a valid message is one that has at least 10 non-special characters.&lt;/p&gt;

&lt;p&gt;So, in the &lt;code&gt;pages/index.js&lt;/code&gt; file, we'll make the following changes to the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&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;useState&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;react&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;MIN_NAME_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;MIN_MESSAGE_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;formDefaults&lt;/span&gt; &lt;span class="o"&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;form-name&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;contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validatedDefault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&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="na"&gt;message&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;MIN_NAME_LENGTH&lt;/code&gt; and &lt;code&gt;MIN_MESSAGE_LENGTH&lt;/code&gt; constants will be used to validate the name and message fields, the &lt;code&gt;formDefaults&lt;/code&gt; constant will be used to set/reset the form fields, and the &lt;code&gt;validatedDefault&lt;/code&gt; constant will be used to set the initial state of the form validation.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;formDefaults&lt;/code&gt; constant above, the &lt;code&gt;form-name&lt;/code&gt; key will be mirroring the role of the crucial &lt;code&gt;&amp;lt;input type="hidden" name="form-name" value="contact" /&amp;gt;&lt;/code&gt; tag we added at the beginning of our form, that's required by Netlify. Because we're using React / Next JS, and submitting the data in a state variable, I'm adding that key to the state variable, as it needs to be POST'ed when the form is submitted. The value of the &lt;code&gt;form-name&lt;/code&gt; key &lt;strong&gt;&lt;em&gt;must match the &lt;code&gt;name&lt;/code&gt; attribute of the form&lt;/em&gt;&lt;/strong&gt; or Netlify won't be able to identify the form.&lt;/p&gt;

&lt;p&gt;Next, we'll add the following code to the top of the &lt;code&gt;Home&lt;/code&gt; component, right above our &lt;code&gt;return&lt;/code&gt; statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;formContents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFormContents&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formDefaults&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;validated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValidated&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validatedDefault&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;errorMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setErrorMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&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;handleChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setFormContents&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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;setErrorMessage&lt;/span&gt;&lt;span class="p"&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;validated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;validateFields&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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="nf"&gt;validateFields&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&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;/&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="s1"&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;headers&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;Content-Type&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;application/x-www-form-urlencoded&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="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;formContents&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="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&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="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;res&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;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Message sent!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nf"&gt;setFormContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formDefaults&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;setErrorMessage&lt;/span&gt;&lt;span class="p"&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&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="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setErrorMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;`Oops, looks like there was an error while sending your message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="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;validateFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tempValidated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formContents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;A-Z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&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="nx"&gt;MIN_NAME_LENGTH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setValidated&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&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;tempValidated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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="nf"&gt;setValidated&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;formContents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;A-Z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&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="nx"&gt;MIN_MESSAGE_LENGTH&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setValidated&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&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;tempValidated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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="nf"&gt;setValidated&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tempValidated&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;In the above code, the &lt;code&gt;handleChange&lt;/code&gt; function will simply update the state object keys, clear any existing error message, and then call the &lt;code&gt;validateFields&lt;/code&gt; function if the field that was changed was previously invalid.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;handleSubmit&lt;/code&gt; function will prevent the default form submission, call the &lt;code&gt;validateFields&lt;/code&gt; function, and if the fields are valid, it will POST the form data to Netlify using a fetch() promise, and then reset the form fields and clear any existing error message. If there was an error while submitting the form, it will set the error message to display the error, otherwise it will display a simple success alert.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update the JSX to Work with the JavaScript
&lt;/h2&gt;

&lt;p&gt;Now that our JavaScript code is in place, we'll need to update the JSX to work with it.&lt;/p&gt;

&lt;p&gt;First, we'll add a submit handler (&lt;code&gt;onSubmit={handleSubmit}&lt;/code&gt;) below the &lt;code&gt;data-netlify-honeypot&lt;/code&gt; attribute to the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tag as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'flex flex-col space-y-4'&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'contact'&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'POST'&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'/'&lt;/span&gt;
  &lt;span class="na"&gt;data-netlify&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'true'&lt;/span&gt;
  &lt;span class="na"&gt;data-netlify-honeypot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'bot-sniffer'&lt;/span&gt;
  &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add an &lt;code&gt;onChange={handleChange}&lt;/code&gt; attribute to each of the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; tags. This will allow us to update the state variables as the user types in the form fields. Then we'll modify the code to add error display &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;'s below the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt; fields, as such:&lt;/p&gt;

&lt;p&gt;For the name input field, modify it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'flex flex-col'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text-lg font-bold'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Name&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
    &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'border border-gray-300 rounded-md'&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text'&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;
    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Name (required)'&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formContents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text-red-500 text-sm'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Please enter a name at least
      &lt;span class="si"&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;MIN_NAME_LENGTH&lt;/span&gt; &lt;span class="o"&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="si"&gt;}&lt;/span&gt;
      characters long.
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and for the message textarea field, modify it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'flex flex-col'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text-lg font-bold'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Message&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;
    &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'border border-gray-300 rounded-md'&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'message'&lt;/span&gt;
    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Message (required)'&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formContents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text-red-500 text-sm'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Please enter a message at least
      &lt;span class="si"&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="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;MIN_MESSAGE_LENGTH&lt;/span&gt; &lt;span class="o"&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="si"&gt;}&lt;/span&gt;
      characters long.
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then finally, we'll add one last error display &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; above the submit button, as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'flex justify-center'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'text-red-500 text-sm'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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 1st two error display &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;'s will only show if the user has entered an invalid name or message, whereas the last one will only show if there was an error while submitting the form.&lt;/p&gt;

&lt;p&gt;Whew, that was a lot of changes to make! Hopefully I didn't make any typos or confuse you too much along the way, but if I did, you can always check out the &lt;a href="https://github.com/mlaposta/nextjs-netlify-form/"&gt;GitHub repo&lt;/a&gt; for this post. You can also go straight to the &lt;code&gt;pages/index.js&lt;/code&gt; file &lt;a href="https://github.com/mlaposta/nextjs-netlify-form/blob/master/pages/index.js"&gt;here to check out the final code&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enabling Netlify Form Handling
&lt;/h2&gt;

&lt;p&gt;Ok, so now that we've got the form all set up, we need to enable Netlify form handling.&lt;/p&gt;

&lt;p&gt;Now, I'm going to assume you have a Netlify account already, which is why you're reading this post in the first place. If you don't, you can &lt;a href="https://app.netlify.com/signup"&gt;sign up for a free account on netlify.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm not going to go over the steps to create a new site on Netlify, as that's pretty straight forward. But once you've created your site, you'll need to enable Netlify form handling, which is what we'll be doing next.&lt;/p&gt;

&lt;p&gt;Now if you want to get the details straight from the horses mouth, you can check out &lt;a href="https://docs.netlify.com/forms/setup/"&gt;Netlify's docs on form handling&lt;/a&gt;, which goes over the steps in detail, and also gives HTML and JavaScript setup examples. They don't however give any examples using React or Next JS, which is why I wrote this post.&lt;/p&gt;

&lt;p&gt;So, in order to enable Netlify form handling:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log into your Netlify account and go to your site's dashboard.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Site configuration&lt;/strong&gt; option from the left-hand menu.&lt;/li&gt;
&lt;li&gt;To the right of the main menu, click on &lt;strong&gt;Forms&lt;/strong&gt; to expand the Forms sub-menu, and then either select the &lt;strong&gt;Configuration&lt;/strong&gt; item, or simply scroll down to the bottom of the page.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Form detection&lt;/strong&gt; card, click on the "Enable form detection" button.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Once that's done, you'll simply need to deploy (or redeploy) your site, and Netlify will take care of the rest!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that if you enabled the form detection &lt;strong&gt;&lt;em&gt;after you deployed your site&lt;/em&gt;&lt;/strong&gt;, you'll need to redeploy your site for the form detection to work. That's a mistake I made at first, and was confused when the form wasn't working! 🤣&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Testing the Form
&lt;/h2&gt;

&lt;p&gt;Now that we've got the form all set up, we'll want to test it out to make sure it's working as expected.&lt;/p&gt;

&lt;p&gt;Unfortunately, one of the major drawbacks with this system is that you can't fully test it locally. You can test for simple things like the length of the name and message fields, but you can't test the actual submission of the form.&lt;/p&gt;

&lt;p&gt;To do that, you'll need to deploy your site to a live environment in order to properly test it. This is because Netlify needs to be able to parse the HTML at deploy time, and then inject the necessary JavaScript into the page.&lt;/p&gt;

&lt;p&gt;So after deploying it to my Netlify site, I submitted a basic test message, and then checked the &lt;strong&gt;Forms&lt;/strong&gt; menu item in my Netlify dashboard.&lt;/p&gt;

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

&lt;p&gt;After expanding the contact submission in the &lt;strong&gt;Active forms&lt;/strong&gt; card, my message was there ...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8j9ql6x6k4niokzwagk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8j9ql6x6k4niokzwagk.jpg" alt="Check for form submissions" width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Phew, it worked! 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Email Notifications
&lt;/h2&gt;

&lt;p&gt;You don't need to worry about always checking your Netlify console for new messages, as you can set up email notifications to be sent to you whenever a new message is submitted.&lt;/p&gt;

&lt;p&gt;Doing so is a super simple process, which you can read about in &lt;a href="https://docs.netlify.com/forms/notifications/"&gt;Netlify's docs on form notifications&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;That wraps it up for this post.&lt;/p&gt;

&lt;p&gt;I hope you found it useful, and if you had any issues with your own form, you were able to fix them using this tutorial.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>netlify</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
