<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brian Rinaldi</title>
    <description>The latest articles on DEV Community by Brian Rinaldi (@remotesynth).</description>
    <link>https://dev.to/remotesynth</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%2F2939%2Fa11acfc4-249c-4194-8172-29d0dad6b181.png</url>
      <title>DEV Community: Brian Rinaldi</title>
      <link>https://dev.to/remotesynth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/remotesynth"/>
    <language>en</language>
    <item>
      <title>Navigating the Buzzwords of Frontend Development</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Tue, 30 Apr 2024 11:42:29 +0000</pubDate>
      <link>https://dev.to/remotesynth/navigating-the-buzzwords-of-frontend-development-1ap9</link>
      <guid>https://dev.to/remotesynth/navigating-the-buzzwords-of-frontend-development-1ap9</guid>
      <description>&lt;p&gt;Technical concepts are, by nature, complex and difficult to describe. While developer love to hate buzzwords or jargon, they can serve as very useful shorthand for people "in the know" that simplifies discussing complex concepts by encapsulating them into a single term. They can even help us organize and categorize concepts.&lt;/p&gt;

&lt;p&gt;Let's take a quick example. Imagine I am trying to discuss compute that runs in the cloud and typically scales up or down depending on the needs of the application at any given moment and, though it runs on servers, the developer coding for it doesn't need to worry about the servers. That's a lot of words. You'd be forgiven if you got lost in the middle of them. Many of you may already know the term that I am describing though: serverless. Is the jargon imperfect? Yes, but it's a heck of a lot easier to use than the alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Love To Hate Jargon
&lt;/h2&gt;

&lt;p&gt;On the other hand, jargon's bad rep is not entirely undeserved. It can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exclusionary&lt;/strong&gt; – If you don't already know what it means you can easily get left out or confused by discussions that could be relevant to you. Oftentimes, this is combined with a complete lack of explanation of the underlying terms and can even bring embarrassment or ridicule for those not "in the know".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambiguous&lt;/strong&gt; – In many cases, terms start out clearly defined and, as the technology matures and companies and developers pile into trends, they become blurred and amorphous.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appropriated&lt;/strong&gt; – The more companies jump on the bandwagon, the more a term can seem like a marketing tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers and companies often throw out buzzwords and jargon simply assuming you know what it means. This is frequently done in a way that can even folks feel "stupid" for not immediately knowing terms – especially new developers. Heck, I've been in this industry for over 25 years and I still encounter situations where I am left feeling this way. I'm hoping that this guide helps folks feel a bit more confident in understanding some of the jargon they'll frequently hear as a frontend developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend and Full-stack Development is Full of Jargon
&lt;/h2&gt;

&lt;p&gt;We use a lot of buzzwords in frontend development. I'm not even sure that any article could cover them all. Here's the ones I plan to talk about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jamstack&lt;/li&gt;
&lt;li&gt;Composable&lt;/li&gt;
&lt;li&gt;MACH&lt;/li&gt;
&lt;li&gt;Decoupling&lt;/li&gt;
&lt;li&gt;Headless&lt;/li&gt;
&lt;li&gt;Pre-rendering&lt;/li&gt;
&lt;li&gt;SSR, SSG, CSR, ISR, etc.&lt;/li&gt;
&lt;li&gt;Edge&lt;/li&gt;
&lt;li&gt;Middleware&lt;/li&gt;
&lt;li&gt;Islands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm going to spend a bit more time on Jamstack because it ends up being relevant, in my opinion, to many of the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jamstack
&lt;/h2&gt;

&lt;p&gt;Once upon a time, we had static sites. Tools like Jekyll and GitHub Pages helped popularize the concept back in 2008.&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%2Fsx4znaw3loh1is4b5q4c.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%2Fsx4znaw3loh1is4b5q4c.png" alt="static sites circa 2008" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But static sounded old fashioned and limiting. I know as I tried (unsuccessfully) to convince my company to adopt them for our company blog and talked about them extensively at various events around this time.&lt;/p&gt;

&lt;p&gt;In the hopes of bringing the concept to a broader audience, Matt Biilmann, cofounder and CEO of Netlify, coined the term Jamstack in 2015. This gained a lot of attention when he presented to &lt;a href="https://vimeo.com/163522126"&gt;Smashing Conference in 2016&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The original definition emphasized the speed and security that aligned with static sites sprinkled with client-side JavaScript:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Jamstack is an architecture designed to make the web faster, more secure, and easier to scale. It builds on many of the tools and workflows which developers love, and which bring maximum productivity.&lt;/p&gt;

&lt;p&gt;The core principles of &lt;a href="https://web.archive.org/web/20220718111936/https://jamstack.org/glossary/pre-render"&gt;pre-rendering&lt;/a&gt;, and &lt;a href="https://web.archive.org/web/20220718111936/https://jamstack.org/glossary/decoupling"&gt;decoupling&lt;/a&gt;, enable sites and applications to be delivered with greater confidence and resilience than ever before.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Conceptually, to me, it was like "static sites++". You'd use a static site generator (Jekyll, Hugo, Middleman were the popular options at the time, though Gatsby had just been released) and you'd enhance them with client-side JavaScript for interactivity and some data loading.&lt;/p&gt;

&lt;p&gt;As tools (and Netlify) evolved though, so did the &lt;a href="https://www.netlify.com/blog/the-jamstack-definition-evolved/"&gt;official definition&lt;/a&gt; in 2022 (&lt;a href="https://www.smashingmagazine.com/2021/05/evolution-jamstack/"&gt;more details here&lt;/a&gt;). The new definition got much more ambiguous because it attempted to incorporate things like server rendering, serverless, headless and a bunch of other terms we'll discuss here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Jamstack is an architectural approach that decouples the web experience layer from data and business logic, improving flexibility, scalability, performance, and maintainability.&lt;/p&gt;

&lt;p&gt;Jamstack removes the need for business logic to dictate the web experience. It enables a composable architecture for the web where custom logic and 3rd party services are consumed through APIs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The biggest change was the removal of pre-rendering from the description. This is what I said in &lt;a href="https://jamstack.email"&gt;my newsletter&lt;/a&gt; at the time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let's explore a question that we've never explored before: what is Jamstack? !🤣 Of course I kid as we, as a community, have perhaps explored this to death lately. However, it is prescient to talk about it again since Netlify has officially &lt;a href="https://jamstack.email/link/126499/39ee9d46c5"&gt;revamped the definition&lt;/a&gt; on Jamstack.org.&lt;/p&gt;

&lt;p&gt;The biggest change is that the definition is now the first thing you see on the site. At first glance you may not even notice any dramatic changes in the text but look closer you'll notice &lt;strong&gt;a primary emphasis on decoupling&lt;/strong&gt; with &lt;em&gt;no mention of pre-rendering&lt;/em&gt;, which was a core tenet of the prior definition (so much so, it was called out with its own subsection). I suspect the trend towards hybrid SSG/SSR or even pure SSR frameworks is driving this change in order to keep Jamstack (and the companies that identify with it) relevant as developer tools evolve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As you may have noticed from both these definitions though, that Jamstack is very focused on the &lt;em&gt;how&lt;/em&gt; you build your web application, from the tools you use to how you retrieve data to the type of output or rendering you choose. It's an umbrella term that encompasses a ton of other terminology (aka jargon).&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%2F6jqeyqje5dzp2nytlmei.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%2F6jqeyqje5dzp2nytlmei.png" alt="Jamstack is an umbrella term" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's worth noting that, though the current usage of Jamstack encompasses all these concepts, they are all separate terms that you can use outside the scope of Jamstack. For example, a site can use SSR that isn't in any way Jamstack. With that being said, let's dig into some of these terms. In fact, while the Jamstack term is still in use, you'll more frequently hear the terms under the umbrella than Jamstack itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is "decoupling"?
&lt;/h3&gt;

&lt;p&gt;Decoupling is one of those terms that is defined mostly by what it isn't. So, to understand decoupling, I think it's easiest to start with understanding the alternative because it's simpler conceptually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monolithic Architecture&lt;/strong&gt; – The frontend and the backend are combined and inseparable.  For example, you can think a more traditional WordPress or Drupal site where all the aspects of the application are lumped together into a single entity. Making changes to the backend requires changes and redeployment of the frontend and vice-vera. Thus the frontend and backend are considered "tightly coupled". &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decoupled Architecture&lt;/strong&gt; – Decoupling is essentially the opposite of this wherein the frontend and backend are independent of each other. The frontend generally gets data from the backend via APIs. This communication is generally isolated into microservices, meaning that, even when a backend change might require a frontend update, the scope of the changes is typically limited. In addition, an update to the backend can be made without requiring a build and redeploy of the frontend and vice-versa.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is headless?
&lt;/h3&gt;

&lt;p&gt;Headless tools often play an important role in a decoupled architecture. A headless tool is one where only the backend exists and any frontend (web, mobile, etc.) can access the data via an API. These tools largely came about to solve the problem of maintaining multiple backends to support both web and mobile apps. The term headless is used to distinguish the tool from alternatives that have traditionally been monolithic such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Headless CMS (ex. Contentful, Sanity, AgilityCMS)&lt;/li&gt;
&lt;li&gt;Headless Commerce (ex. headless versions of Shopify, BigCommerce)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://agilitycms.com/resources/guide/what-is-a-headless-cms"&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%2F7a0j1ph6qg8v9afi0cf7.png" alt="headless CMS versus traditional CMS" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://agilitycms.com/resources/guide/what-is-a-headless-cms"&gt;"What is a headless CMS?" by Agility CMS&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because of the focus on decoupling, headless tools were commonly considered a core piece of the Jamstack ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is "pre-rendering"?
&lt;/h3&gt;

&lt;p&gt;Pre-rendering is just a fancy way of saying static, but, in my opinion, it is more accurate.&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%2F88tnt8p9bv31yfgx3vyz.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%2F88tnt8p9bv31yfgx3vyz.jpg" alt="I like big words" width="460" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I could grab Notepad and create a "static site". On the other hand,  pre-rendering is functionally the same as server-side rendering (SSR) but, instead of happening at request time, it happens at build time. Unlike plain static HTML, this is done by running the output of the page template/components and data and generating the assets as part of the build in the same way as any other server rendered page. The only difference is that this happens at build time, so everyone will receive the same rendered output.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are CSR, SSG, SSR, etc?
&lt;/h3&gt;

&lt;p&gt;So I mentioned rendering and specifically server-side rendering (SSR). Nowadays, there seems to be a new rendering acronym every month. Below are the ones you are most likely to hear about. It is worth noting that these are the very basic definitions that leave out the nuances to how every front-end framework implements each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSR (client-side rendering)&lt;/strong&gt; – Rendering happens in the browser client. This is the way that single-page applications (SPAs) like React, Angular and Vue rendered pages prior to the advent of things like server components. A shell application is loaded and populated with data/content (usually in the form of JSON) that is used to "hydrate" the pages/views. Changing views doesn't reload the whole page, but gets new content/data and updates the rendered output on the client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSR (server side rendering)&lt;/strong&gt; – What's old is new again. Once upon a time this was the only rendering available (it's how I started my career building in ColdFusion). Every request by every user is sent to the server where the output is rendered and the returned to the client (i.e. browser). There are definitely differences between how modern frameworks handle this versus my ColdFusion days, but it's conceptually the same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSG (static site generation)&lt;/strong&gt; – The is the pre-rendering I spoke about earlier. It's a form of server-side rendering done at build time where data and templates are combined to generate static output (primarily HTML, CSS and JavaScript) that can be deployed to the server/CDN as files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incremental Static Regeneration (ISR), Distributed Persistent Rendering (DPR) and Deferred Static Generation (DSG)&lt;/strong&gt; – These are various implementations of the same general concept (yes, there are slight differences in implementation that aren't worth going into detail here). Basically, think of this as "deferred rendering", which is like if SSR and SSG had a baby. The request is rendered upon the initial request on the server as static and subsequent requests get the static response (similar to a long-term or persistent cache). It is generally a way to reduce the build times of large sites but still maintain most of the speed benefits of pre-rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composable
&lt;/h2&gt;

&lt;p&gt;Developers sometimes complain that the only distinction between Jamstack and composable is that the latter is more "enterprisey". This isn't entirely wrong, as the marketing for Jamstack tools tended to be very bottoms-up (i.e. targeting developers directly), while composable solutions are usually more expensive enterprise solutions.&lt;/p&gt;

&lt;p&gt;However, there is more to the distinction.&lt;/p&gt;

&lt;p&gt;The term composable is primarily concerned with the backend of your application than either how you build that application or even what type of application it is. Jamstack, as we noted, is more about how you build the frontend of specifically web applications. Composable is about creating a backend that can serve all the various frontends your company may support.&lt;/p&gt;

&lt;p&gt;Composable architectures address a problem that arises when you adopt decoupling and headless. That problem is that suddenly you are combining data from a wide variety of sources. Take for instance the diagram shown below wherein an e-commerce site has product data, payment APIs, customer data, inventory APIS and much more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://commercetools.com/blog/how-do-composable-headless-and-mach-compare-the-key-differences-explained"&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%2Fmoq1v5k9dp2z0bkyar4u.png" alt="Composable architecture" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://commercetools.com/blog/how-do-composable-headless-and-mach-compare-the-key-differences-explained"&gt;"How do composable, headless and MACH compare? The key differences explained" by CommerceTools&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Composable tools tend to target enterprises because they tend to feel this problem more acutely, relying on many sources for their data (internal APIs, external APIs, databases, third-party services, etc.). The concept of composable architecture combines all of these sources into a single, consolidated backend API (often using GraphQL). This can greatly simplify the code and architecture of an application because the decoupled frontend doesn't need to concern itself with all the various data sources – it only needs to access the composed data layer. It also makes the backend more "plug and play" in the sense that you can choose the right tool for each job and plug them into the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  So... Jamstack + Composable?
&lt;/h3&gt;

&lt;p&gt;Yep. A composed backend API can be a part of a Jamstack application architecture. The two terms are not mutually exclusive.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about MACH?
&lt;/h3&gt;

&lt;p&gt;MACH stands for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;M&lt;/strong&gt;icroservices &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A&lt;/strong&gt;PI-first &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C&lt;/strong&gt;loud-native SaaS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;H&lt;/strong&gt;eadless&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is somewhat interchangeable with composable (and, in fact, composability is one of the core tenets) but a bit more specific in terms of the nature of the stack. The concept is pushed by the &lt;a href="https://machalliance.org/"&gt;MACH Alliance&lt;/a&gt;, a non-profit industry group supported by a number of companies within the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Edge
&lt;/h2&gt;

&lt;p&gt;To understand the edge, you have to understand what a Content Delivery Network (CDN) is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CDN&lt;/strong&gt; – A CDN is a globally distributed group of servers that allows you to serve content closer to the user's geographic location. This was traditionally just static assets (ex. you might have an image CDN) but it played an important role the origin of Jamstack. The static assets that are generated as part of your web app such as Netlify, Vercel or Cloudflare are deployed across their CDN network. It's part of what made Jamstack fast.&lt;/p&gt;

&lt;p&gt;The common usage of the term edge nowadays is generally referring to compute (think serverless functions) that runs at the CDN level ("at the edge"), close to the users. This can reduce the latency running compute compared to calling a typical serverless function deployed to a single region, but that is far from universal and edge compute can even cause additional latency in some cases (though that's an article in itself).&lt;/p&gt;

&lt;p&gt;There is also a lot of variance across providers in terms of the number and geographic distribution of edge nodes/regions. Some vendors have hundreds of edge nodes while others have maybe a dozen. The importance of this really depends on the type of application that you are building and the type of compute you are running at the edge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Middleware
&lt;/h3&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%2Fu0qo2ch8kaz2zinhqmg5.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%2Fu0qo2ch8kaz2zinhqmg5.png" alt="middleware" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the interesting capabilities of edge functions beyond the potential for reduced latency is that they can intercept a user's request and response. This allows them to modify or redirect either, opening up a ton of use cases that can be done without less performant client-side or server-side solutions. Middleware is a term most often used to describe a capability within a full-stack framework that intercepts the request and/or response to the server using edge compute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Islands
&lt;/h2&gt;

&lt;p&gt;Islands is a term that has only more recently gained a lot of attention. It's a web architecture that aims to address the problem of ever increasing JavaScript bundle sizes for applications, particularly the single-page applications (SPAs) built using frameworks like React, though it tends to be targeted more at content-focused web apps. It's become popular via newer web application frameworks, notably Astro.&lt;/p&gt;

&lt;p&gt;In a SPA, the entire page is interactive and loaded via JavaScript, regardless of whether what it is displaying requires the interactivity. However a typical page is generally some combination of static content and interactive elements. Your site's home page, using Jason Miller's canonical example shown below, may have an interactive navigation and image carousel, but the other aspects do not require interactivity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jasonformat.com/islands-architecture/"&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%2Fhfiyqct9k2kqey1kh75d.png" alt="islands architecture diagram" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://jasonformat.com/islands-architecture/"&gt;Islands Architecture by Jason Miller&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In an islands architecture, only those interactive elements get the necessary JavaScript to run rather than the entire page. This can potentially reduce the JavaScript bundle by a substantial amount depending on the nature of the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  But, wait, there's more...
&lt;/h2&gt;

&lt;p&gt;Ultimately, this was always going to be a very incomplete list. There are plenty of buzzwords I didn't have time to cover and plenty more I may not even know myself. Plus, new ones are created all the time, so keeping up can be difficult. Still, I hope that this has been helpful to at least give you a foundation in some of the terms you may have been unfamiliar with.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>TheJam.dev 2024 - A Free, 2-day Virtual WebDev Conference</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Thu, 18 Jan 2024 13:09:43 +0000</pubDate>
      <link>https://dev.to/remotesynth/thejamdev-2024-a-free-2-day-virtual-webdev-conference-n7</link>
      <guid>https://dev.to/remotesynth/thejamdev-2024-a-free-2-day-virtual-webdev-conference-n7</guid>
      <description>&lt;p&gt;The web development, JavaScript and serverless tools and technologies move incredibly fast. It can be hard to keep up. Events have always played a role, not just in keeping me up-to-date and informed, but also getting me excited about what I can build. Those of you that know me know that I run a ton of events, and my biggest one each year since 2021 is &lt;a href="https://thejam.dev" rel="noopener noreferrer"&gt;TheJam.dev&lt;/a&gt;. This year's event is happening on January 24-25 from 11am-5pm ET (UTC -5). I know I'm biased but I think this is one of the best (if not the best) web development focused events of the year. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Speakers&lt;/strong&gt; – Every event is only as good as it's speakers, and this year we've got an incredible lineup featuring Cassidy Williams, James Q Quick, Matt Biilmann, Alex Russell, Salma Alam-Naylor and so many more. Honestly, I feel like we have an entire lineup of keynote-worthy speakers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's completely free&lt;/strong&gt; – Yep, no cost. Nada. Come join us live and be part of the conversation and even ask the speakers your questions live or watch the recording as soon as the event is done. You don't need to pay for flights, hotels, conference tickets and yet you still get to see some of the best speakers giving important talks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I hope you'll join me there next week!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thejam.dev" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcfe.dev%2Fimg%2Fbanners%2FThe-Jam-Dev-2024.jpg" alt="TheJam.dev 2024"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>serverless</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>What is Jamstack in 2024?</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Mon, 15 Jan 2024 20:38:58 +0000</pubDate>
      <link>https://dev.to/remotesynth/what-is-jamstack-in-2024-3kc6</link>
      <guid>https://dev.to/remotesynth/what-is-jamstack-in-2024-3kc6</guid>
      <description>&lt;p&gt;This is my fourth year doing this update. Some of you may be wondering, "But Brian, didn't you already proclaim Jamstack dead?" Back in July, I did write a post titled "&lt;a href="https://remotesynthesis.com/blog/goodbye-jamstack/"&gt;Goodbye Jamstack&lt;/a&gt;" in response to the end of the Jamstack Discord Community.&lt;/p&gt;

&lt;p&gt;My argument was that, if &lt;a href="https://remotesynthesis.com/blog/jamstack-in-2023/"&gt;last year's update&lt;/a&gt; proclaimed "Jamstack has become more of a “community” than a set of architectural rules," what happens when there is no longer a place for the community? What happens when everyone filters off into their tool/framework-specific Discord/Slack and there's no longer opportunities to gather and communicate?&lt;/p&gt;

&lt;p&gt;But there was nuance to my conclusion that I felt many follow ups missed (emphasis added).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The term seems to be dead &lt;strong&gt;but the tools and technologies it encompassed are still very much alive&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Entering 2024, it still feels like the term is on life support, though there may be a tinge of remorse about that. However, the tools that fell under the Jamstack umbrella continue to grow in adoption.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Interested in the tools and technologies of Jamstack? Don't miss &lt;a href="https://thejam.dev"&gt;TheJam.dev on January 24-25&lt;/a&gt; featuring speakers including Alex Russell, Cassidy Williams, James Q Quick, Matt Biilmann, Zach Leatherman, Salma Alam-Naylor and many more. It's free!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A Brief 2023 Timeline
&lt;/h2&gt;

&lt;p&gt;First, here's a (very incomplete) list of some of the big announcements in 2023:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;January&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.11ty.dev/blog/eleventy-v2-beta/"&gt;First beta of Eleventy 2 release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://astro.build/blog/astro-2/"&gt;Astro 2.0 adds type-safety for Markdown and MDX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;February&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://thenewstack.io/netlify-acquires-gatsby-its-struggling-jamstack-competitor/"&gt;Netlify acquires Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;March&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.sitepoint.com/eleventy-2/"&gt;Eleventy 2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;May&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://vercel.com/blog/vercel-storage"&gt;Vercel adds storage APIs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://deno.com/blog/kv"&gt;Deno adds long-awaited KV stor&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nextjs.org/blog/next-13-4"&gt;Next 13.4 makes the app router stable&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;June&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://blog.cloudflare.com/making-cloudflare-for-web/"&gt;Lots of big Cloudflare announcements for Workers and Pages&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://astro.build/blog/astro-250/"&gt;Astro 2.5 adds data collections&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;July&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.netlify.com/blog/introducing-netlify-connect/"&gt;Netlify announces Netlify Connect&lt;/a&gt;...&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;...&lt;a href="https://www.netlify.com/blog/ceo-announcement-to-the-netlify-team/"&gt;But also announces a major restructuring&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;August&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://starlight.astro.build/"&gt;Astro announces Starlight for documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;September&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.netlify.com/blog/gatsby-cloud-evolution/"&gt;Netlify clarifies the future of Gatsby&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://astro.build/blog/astro-3/"&gt;Astro 3.0 makes view transitions stable&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://tom.preston-werner.com/2023/05/30/redwoods-next-epoch-all-in-on-rsc.html"&gt;RedwoodJS goes all in on React Server Components&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;October&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.netlify.com/blog/introducing-netlify-functions-2-0/"&gt;Netlify updates to Functions 2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;November&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://nextjs.org/blog/next-14"&gt;Next.js 14 declares Server Actions stable&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://remix.run/blog/remix-heart-vite"&gt;Remix adds Vite support&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.netlify.com/blog/introducing-netlify-image-cdn-beta/"&gt;Netlify releases an image CDN&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;December&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://astro.build/blog/astro-4/"&gt;Astro 4 updates to Vite 5&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.netlify.com/blog/unveiling-the-state-of-web-development-and-predictions-for-2024-and-beyond/"&gt;Netlify's State of Web Development&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Community Held a Funeral for Jamstack...
&lt;/h2&gt;

&lt;p&gt;In retrospect, trying to convey the nuance between a &lt;em&gt;term&lt;/em&gt; being dead and the category of technology it described being dead was always going to be difficult. If it was possible, it appears my post didn't accomplish it because it was shortly followed by a long list of tweets declaring Jamstack dead.&lt;/p&gt;

&lt;p&gt;Many folks didn't really seem to see this as a big loss. Tyler from UI.dev declared:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even though the term was non-prescriptive and imprecise (and thus mostly unhelpful) from the start, Jamstack &lt;em&gt;did&lt;/em&gt; help kickstart a movement around modern static sites in the late 2010s. But it was also clearly a marketing term that Netlify overextended to the point where it eventually lost all its original meaning — however nebulous that was to begin with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Swyx argued that &lt;a href="https://www.swyx.io/netlify-era-jamstack-end"&gt;the community was ambivalent about the term, at best, anyway&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;a href="https://css-tricks.com/jamstack-vs-jamstack/"&gt;rebrand from JAMstack to Jamstack&lt;/a&gt; felt like a distinction without a difference (which is why I continue to capitalize the old way). When the frontend metaframework metagame moved from 100% static rendering to mostly-static to mostly-serverless-and-sometimes-edge, JAMstack’s advocates pivoted adroitly, claiming this fit JAMstack all along, which means it fit nearly everything, which means it stood for nearly nothing. When even a VP is saying ”&lt;a href="https://dev.to/ryansolid/when-netlify-asks-you-to-full-time-oss-you-say-yes-5ccf"&gt;Jamstack is a feeling&lt;/a&gt;”, what he doesn’t say is that feeling is most often &lt;em&gt;ambivalence&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jared White agrees, arguing that Jamstack's downfall began when it lost sight of the simplicity it offered in favor of complexity that sold services:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Along with this “second generation” Jamstack mindset shift came &lt;em&gt;an order of magnitude&lt;/em&gt; more build complexity. Instead of a straightforward CLI kicking off simple transformations to go from Markdown -&amp;gt; HTML plus concatenate some (S)CSS files together or whatever, you’d get multi-minute long builds and GBs of &lt;code&gt;node_modules&lt;/code&gt;...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Publicly, Netlify and its employees (summed up nicely &lt;a href="https://www.javascriptjam.com/august-1-2023/"&gt;here&lt;/a&gt;) responded with a refrain that argued that Jamstack isn't dead, the term is just not necessary anymore because it won. They compared it to terms like Responsive Web Design that are rarely used anymore because they are so ubiquitous that the term becomes pointless. Here's how &lt;a href="https://news.ycombinator.com/item?id=36947761"&gt;Matt Biilmann put it&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I would actually argue that Jamstack has won to the point of basically just being "Modern Web Development" by now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He also clarified to &lt;a href="https://thenewstack.io/is-jamstack-toast-some-developers-say-yes-netlify-says-no/"&gt;The New Stack&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Very much not dropping the term or declaring the architecture gone!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But, while Jamstack.org still exists, the term has seen a dramatic decline in usage in the community, and is no longer part of Netlify's core messaging.&lt;/p&gt;

&lt;h2&gt;
  
  
  ...But Is the Term Actually Dead?
&lt;/h2&gt;

&lt;p&gt;The term still has strong defenders though, most notably, Zach Leatherman, creator of Eleventy. He started by gathering a number of folks with strong opinions on the topic to discuss it in what he called a &lt;a href="https://www.zachleat.com/web/jamstack-zhuzh/"&gt;Zhuzh&lt;/a&gt; and then followed it up with a post on &lt;a href="https://www.zachleat.com/web/jamstack-future/"&gt;what the future of Jamstack could look like&lt;/a&gt;. His goal?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To refocus the definition and pull it back from post-pivot silver-bullet marketing, which was arguably an overstep attempt to pull dynamic/SSR/on-request frameworks like (non-static export) Next.js, Remix, Fresh (et al) under the umbrella.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To that end, he and his colleagues at CloudCannon even launched a new site called &lt;a href="https://thefutureofjamstack.org/"&gt;The Future of Jamstack&lt;/a&gt;. The general idea seems that maybe Jamstack doesn't have to be the solution to everything. Maybe a narrower term closer to its origins can make the distinction of Jamstack versus traditional web development clearer and could revive the community, albeit on a smaller scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Where Does That Leave Us?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't it always seem to go&lt;br&gt;
That you don't know what you've got 'til it's gone&lt;/p&gt;

&lt;p&gt;&lt;a href="https://g.co/kgs/SMMkB9W"&gt;Joni Mitchell, Big Yellow Taxi&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I am fortunate that my job gives me the opportunity to talk to a lot of developers and, when it comes to the Jamstack, I could summarize many recent discussions with: "Maybe Jamstack did describe something after all?"&lt;/p&gt;

&lt;p&gt;Web development describes an incredibly and increasingly broad spectrum of tools, techniques and technologies. It could mean building full stack applications in a language like Ruby and Rails. Or full stack JavaScript using a tool like Next.js deployed to Vercel (and AWS, Cloudflare, etc. under the covers). Or maybe it's a static site using Eleventy or Hugo that focuses on content but adds some services for dynamic functionality. Or even a simple marketing site built with Squarespace or Wix.&lt;/p&gt;

&lt;p&gt;Sometimes these distinctions aren't relevant but most of the time they are. I don't want to go to a conference, join a community, purchase training, buy a book, etc. only to find out that none of it is relevant to me. Web development is just too big an umbrella.&lt;/p&gt;

&lt;p&gt;I also prefer that we all don't just further divide off into ultra-niche communities largely run by for-profit companies to support their framework/tools but, unfortunately, that is where I feel that we are right now. It turns out the term, as amorphous as it was, was a nice shortcut for a range of tools and technologies that spanned companies and projects. Trying to reach to a Jamstack audience without it is just laundry-listing a range of things, as I've largely done when promoting &lt;a href="https://thejam.dev"&gt;TheJam.dev&lt;/a&gt; or my &lt;a href="https://cfe.dev/newsletters/jamstacked/"&gt;Jamstacked newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.netlify.com/blog/unveiling-the-state-of-web-development-and-predictions-for-2024-and-beyond/"&gt;Every&lt;/a&gt; &lt;a href="https://risingstars.js.org/2023/en#section-ssg"&gt;single&lt;/a&gt; &lt;a href="https://2022.stateofjs.com/en-US"&gt;data point&lt;/a&gt; indicates that everything that arguably sat under the Jamstack umbrella continues to grow is adoption and popularity, but I don't foresee anyone turning back the tide for the term itself. So barring dramatic changes in 2024, this will likely be the last of these updates (note that the event and the newsletter will continue, focusing on that laundry list of topics that are still relevant). Perhaps 2024 will bring us new ways (or terms) to talk about how we build web sites.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>jamstack</category>
      <category>javascript</category>
    </item>
    <item>
      <title>What to Expect for Developers in 2024 (My Predictions)</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Tue, 02 Jan 2024 19:46:25 +0000</pubDate>
      <link>https://dev.to/remotesynth/what-to-expect-for-developers-in-2024-my-predictions-108o</link>
      <guid>https://dev.to/remotesynth/what-to-expect-for-developers-in-2024-my-predictions-108o</guid>
      <description>&lt;p&gt;I enjoy reading folks predictions for the next year. Regardless of how accurate they ultimately are, I find them to be a good sense of where we stand from that person's unique perspective and areas of expertise. I've never done predictions before, but, at the risk of looking like a fool a year from now, I'm gonna give it a shot.&lt;/p&gt;

&lt;p&gt;First, a little bit of context for those who don't know me. My areas of expertise are around front-end web development, JavaScript, full-stack development, serverless, developer tools, developer events and developer relations (aka DevRel). So, with that in mind, here goes my 4 predictions for 2024.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developers Begin to Flee Complexity
&lt;/h2&gt;

&lt;p&gt;Every year seems to bring added layers of complexity into the work of front-end and full stack development. React and Next.js seem to dominate these areas. Next.js new app router was a significant shift for the framework that, based on what I am seeing, folks are still not quite adjusted to, but this year added more big changes like partial prerendering and server actions.&lt;/p&gt;

&lt;p&gt;Look, I can sometimes be a curmudgeon, but if things felt complex before, they certainly aren't any easier today. Next.js offers so many kinds of rendering that it can be difficult to keep up (static/prerendered, partial prerendering, server rendering/SSR, edge rendering). Yes, each of the new rendering options offers some important improvements, but the added complexity can make debugging difficult, integrations more complicated and can limit your deployment options.&lt;/p&gt;

&lt;p&gt;The good news is that there are a lot of new options including Astro, Enhance, Eleventy, SvelteKit, SolidJS, Qwik, and others. I don't think we're going to see a mass exodus from React, but I think we're already seeing a plateau in adoption (granted, it's become so ubiquitous, a leveling off or even small decline is not so bold a prediction), but I think we'll see more developers experimenting with these other options in their projects. Astro seems to have a ton of momentum and, with its React support, offers a smoother adoption and migration path, and I think this could be the year Astro breaks out into more mainstream adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Becomes Ubiquitous but a Lot of AI Tools Disappear
&lt;/h2&gt;

&lt;p&gt;You may be thinking, "First, I thought AI was already ubiquitous and, second, doesn't this contradict itself?"&lt;/p&gt;

&lt;p&gt;While AI has been everywhere in terms of discussion, new announcements and the hot topic at conferences, my experience has been that that hasn't fully translated into widespread adoption. When I ask folks if they use an AI coding assistant, the majority seem to respond that they are watching carefully from the sideline but have not fully adopted one yet. In many cases, this is because their employer hasn't adopted it yet (and doing so does have legitimate concerns companies need to address).&lt;/p&gt;

&lt;p&gt;That being said, I think this is the year that AI tools like Copilot make huge inroads into companies, thereby making them available to the broader developer audience. I think companies finally weigh the productivity gains over the potential risks in favor of the former.&lt;/p&gt;

&lt;p&gt;We've also seen a flurry of new AI-based tools come out in 2023 including a mad rush of new startups focused on AI. The problem is, AI is expensive right now and even companies like Microsoft are reportedly losing significant amounts of money per &lt;em&gt;paying user&lt;/em&gt; of tools like Copilot. It takes very deep pockets to survive when you lose money for every paying user you sign up (never mind the free plans or trials). I think a lot of these small tools get picked up by the larger companies or simply fade out quickly because the economics don't work and I don't foresee costs declining quickly enough to alter that.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Developer Conferences Shut Down but the Winners Continue to Grow
&lt;/h2&gt;

&lt;p&gt;I wrote a bit about the decline of developer events at the start of last year, but the trend seems to largely be continuing, with a caveat. At the start of 2023, it seemed that most developer events, even the large ones, had been unable to fully recover from the pandemic shut downs. Yes, audiences had returned, but not to pre-pandemic levels. A combination of legitimate health concerns as well as tighter travel and training budgets seemed to be hitting events hard and a number have already called it quits.&lt;/p&gt;

&lt;p&gt;However, my observations over the course of the year made it seem to me that some larger events were thriving while others still struggled. The ones that seemed to be returning to pre-pandemic size or even growing beyond that are what I will refer to as "tentpole" events. These are very large, very well known event (that, in some cases, are tied to large corporations with big pockets for marketing events). On the other hand, the smaller and/or independent events seemed to continue to struggle. &lt;/p&gt;

&lt;p&gt;I think these trends will continue and the tentpole events will become larger and/or harder to get tickets for while more independent or small events will decide to close up shop. I suspect the audience for developer conferences has shrunk overall (due largely to the pandemic) but travel/training budget limitations are forcing attendees to pick winners and losers. If I can only attend one event a year, for instance, I will probably choose a tentpole event as a solid investment over a smaller conference (it is likely an easier sell to my employer too). I don't see those budgets opening up, nor the folks who've written off conferences since the pandemic returning in 2024, which will cause some more disruptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  DevRel Adjusts to a New Realities (Show Me the Numbers!)
&lt;/h2&gt;

&lt;p&gt;This is another trend that I saw in 2023 that I think will only continue to gain steam in 2024, which is the DevRel teams are going to have less freedom and will need to focus on lead and revenue generating activities backed up by metrics. For a long time, DevRel was given a lot of leeway. We (legitimately) argued that the benefit of our activities were difficult to measure accurately and so often relied on "fuzzy" goals. We (legitimately) argued that DevRel works best when it isn't tied to typical marketing or sales goals around lead generation, pipeline and revenue. Those were, unfortunately and to use an overloaded phrase, "zero interest rate phenomenon."&lt;/p&gt;

&lt;p&gt;With the start of the new year and new annual goals being set, my expectation is that a lot of DevRel teams, many of which are already smaller than a year ago, are going to find themselves tied to more activities that are lead generating, with numbers to back them up. I think travel budgets will stay constrained and many DevRels will have to limit speaking to sponsored events (or look for travel assistance from events). And I think more DevRels will find themselves supporting revenue-related activities supporting sales and customer success teams.&lt;/p&gt;

&lt;p&gt;Given these shifts as well as the still slow DevRel job market, I think some folks will decide to move back into engineering or other areas like product management and product marketing. Despite these changes though, I think DevRel will remain a great career for those who adjust.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devrel</category>
      <category>ai</category>
      <category>community</category>
    </item>
    <item>
      <title>Updating Your Netlify Functions to 2.0</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Sun, 26 Nov 2023 15:23:53 +0000</pubDate>
      <link>https://dev.to/remotesynth/updating-your-netlify-functions-to-20-1fm1</link>
      <guid>https://dev.to/remotesynth/updating-your-netlify-functions-to-20-1fm1</guid>
      <description>&lt;p&gt;Netlify recently announced their &lt;a href="https://www.netlify.com/blog/introducing-netlify-functions-2-0/"&gt;Netlify Functions 2.0&lt;/a&gt;, which offer a new functions API more in line with web standards, custom endpoints, advanced routing and more. However, if you built your site for the previous version of Netlify Functions, you'll need to make some updates to take advantage of these features.&lt;/p&gt;

&lt;p&gt;I recently updated the functions that run for &lt;a href="https://cfe.dev"&gt;CFE.dev&lt;/a&gt; and will share the steps I made to update them. Don't worry, it's really straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert the file to an ESModule
&lt;/h2&gt;

&lt;p&gt;My old functions were written CommonJS format with a &lt;code&gt;.js&lt;/code&gt; extension. The first thing you need to do is rename the file with a &lt;code&gt;.mjs&lt;/code&gt; extension which is for the ESModule format that newer functions use. Since my site's functions were &lt;em&gt;really old&lt;/em&gt;, I also moved them from their prior folder structure, to the new default location of &lt;code&gt;/netlify/functions&lt;/code&gt;. This last part isn't necessary but got my site structure in line with Netlify's more recent standards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change requires to imports
&lt;/h3&gt;

&lt;p&gt;ESModule format uses the &lt;code&gt;import&lt;/code&gt; syntax rather than the &lt;code&gt;require()&lt;/code&gt; one. This means you'll need to update all of your imports from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Mailjet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-mailjet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Mailjet&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;node-mailjet&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;h3&gt;
  
  
  Update the handler syntax
&lt;/h3&gt;

&lt;p&gt;The old handler looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The updated handler uses just a slightly different syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="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;context&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Return Response objects
&lt;/h3&gt;

&lt;p&gt;The other changes were extremely simple, but this one can require a little more work. The new functions require that you return a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response"&gt;Response object&lt;/a&gt; while the old ones did not. You'll need to make sure that anywhere that you are passing back a response from the handler, that it returns a response object.&lt;/p&gt;

&lt;p&gt;For example, my old returns looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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;email&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;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email query parameter required&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="c1"&gt;// do stuff&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good news! You've been added.&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="na"&gt;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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 updated responses now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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;email&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email and list query parameters are required&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;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="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// do stuff&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good news! You've been added.&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not difficult but, depending on your code, may require a little time and effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a custom path
&lt;/h3&gt;

&lt;p&gt;This isn't required, but, now that you're using the 2.0 syntax, you might as well use a custom path. This means that instead of the &lt;code&gt;/.netlify/functions/&amp;lt;function name&amp;gt;&lt;/code&gt;, I can tell Netlify what URL I want the API to use. It's super easy. Just add a config export to the end of the function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/mailjet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the function that used to be at &lt;code&gt;/.netlify/functions/mailjet&lt;/code&gt; is now available at &lt;code&gt;/api/mailjet&lt;/code&gt;. Since my API isn't for external consumption, there's no real benefit to my application here other than it is a cleaner and shorter URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Welcome to 2.0
&lt;/h2&gt;

&lt;p&gt;That's all you really need to do. At this point, the function isn't taking advantage of the new 2.0 features other than the custom API path, but it's ready to when I want to.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>Building a (Virtual) Events Site</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Tue, 21 Nov 2023 17:25:08 +0000</pubDate>
      <link>https://dev.to/remotesynth/building-a-virtual-events-site-4h2f</link>
      <guid>https://dev.to/remotesynth/building-a-virtual-events-site-4h2f</guid>
      <description>&lt;p&gt;Back in the summer of 2017, I embarked on creating a site to host events. The site was called Certified Fresh Events, which, for simplicity's sake, I eventually shortened to &lt;a href="https://cfe.dev"&gt;cfe.dev&lt;/a&gt; Honestly, the plan wasn't to host only virtual events, but that is what it's been for 6 1/2 years. In this post, I will talk about how the site is built – what technologies it uses under the covers, what services it relies on and what supporting tools I use.&lt;/p&gt;

&lt;p&gt;Ultimately the structure has been flexible enough to expand from smaller "meetup" style virtual events, to full virtual conferences, to handling live streams and interview style shows. And, the costs are (relatively) low, meaning I don't go broke running it. You can actually check out the &lt;a href="https://github.com/remotesynth/certifiedfreshevents"&gt;source code on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's dive into the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Actual Site
&lt;/h2&gt;

&lt;p&gt;The first thing to know about the site is that it is actually pretty large, with 1253 pages as of today, but also fully static. That doesn't mean it isn't "dynamic" from a user perspective.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hugo
&lt;/h3&gt;

&lt;p&gt;The site has been built with the &lt;a href="https://gohugo.io"&gt;Hugo static site generator&lt;/a&gt; since day one. Back in 2017, when I was originally building the site, I was a big fan of Hugo for its flexibility and speed. Hugo, which is built in Go, is known for extremely fast build times (for example, my 1200+ pages generate in about 7 seconds). This wasn't important when I built it, but it has been beneficial as the site grew large. And, while a lot has changed in the web development framework world since 2017, Hugo still does what I need it to.&lt;/p&gt;

&lt;p&gt;Hugo is very good at handling content, so there are some built in tools that have come in handy over the years. For example, building related content can be difficult and often requires either a manual association or building some kind of algorithm to score related content. In Hugo, this is built in.&lt;/p&gt;

&lt;p&gt;First, I add some details to the Hugo configuration file to tell it how to score related content. In this case, I am giving the highest weight to categories and tags and a slight weight to date adjacency. This isn't a perfect scoring algorithm (it probably needs more thought) since either a category or a tag will put it over the scoring threshold, but it works well enough for my needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;related&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;includeNewer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;toLower&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;indices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;categories&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tags&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;date&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I can query the related pages for any page. The following line pulls only related content from the sessions section of my content. Yes, the syntax in Hugo can be a little awkward at first (or, at least, it was for me).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="k"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$related&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;where&lt;/span&gt; &lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="nv"&gt;Site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;RegularPages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Related&lt;/span&gt; &lt;span class="nv"&gt;.&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"Section"&lt;/span&gt; &lt;span class="s2"&gt;"sessions"&lt;/span&gt; &lt;span class="k"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another thing that has been useful over the years has been Hugo's asset pipeline. This lets me combine JavaScript and CSS files as well as do things like resize images into multiple sizes at build time. I know this isn't unique to Hugo as most SSGs have it, but it is useful nonetheless. The early iteration of this site required me to manually export image sizes, which was laborious. I will admit, I am still not using this feature to its full capabilities.&lt;/p&gt;

&lt;p&gt;The first step is to put assets in the assets folder. Right now this includes my JavaScript, CSS and images. To use CSS, I need to get the assets and then display them (JavaScript works similarly):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{{ $styles := resources.Get "css/styles.css" | postCSS (dict "config"
"./assets/css/postcss.config.js") }}
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ $styles.RelPermalink }}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For images, I pull the media from the page metadata in this case, resize it and then display the resized image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{{- $src := resources.Get (print "img/banners/" .Params.homepage_banner) -}} {{-
$src := $src.Resize "608x" }}
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"{{ $src.RelPermalink }}"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Banner for {{ .Title }}"&lt;/span&gt;
  &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the other quirky things about the way I set up my site is that an event (which I define as the live, virtual meetup or conference) is made up of one or more sessions and those sessions are made up of one or more speakers. Rather than maintain all of this metadata and content in a single file, I assemble it at build time, which also lets me reuse the session and speaker info (side note, there are also transcripts which are also in a different set of files but are never output independently)&lt;/p&gt;

&lt;p&gt;This means that assembling the event page, for instance, is combining the metadata and content from three files. I make use the the &lt;code&gt;scratch&lt;/code&gt; feature repeatedly to avoid scoping issues (yes, there are likely better ways to do that) and the &lt;code&gt;GetPage&lt;/code&gt; function let's me pull the contents of another file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center font-medium leading-tight avatar-list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  {{ if ne .Params.sessions nil }} {{ range $session := .Params.sessions }} {{
  with site.GetPage (print "/sessions/" $session) }} {{ $.Scratch.Set "speakers"
  .Params.speakers }} {{ end }} {{ $speakers := $.Scratch.Get "speakers" }} {{
  range $speaker := $speakers }} {{ with site.GetPage (print "/speakers/"
  $speaker) }}
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/img/speakers/{{ .Params.speaker_image }}"&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Avatar for {{ .Title }}"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"inline-block w-12 h-12 border-2 border-white rounded-full"&lt;/span&gt;
    &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"140"&lt;/span&gt;
    &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"140"&lt;/span&gt;
    &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"{{ .Title }}"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  {{ end }} {{ end }} {{ end }}

  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"max-w-xs ml-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{ range $session := .Params.sessions }} {{ with site.GetPage (print
    "/sessions/" $session) }} {{ $.Scratch.Set "speakers" .Params.speakers }} {{
    end }} {{ $speakers := $.Scratch.Get "speakers" }} {{ range $speaker :=
    $speakers }} {{ with site.GetPage (print "/speakers/" $speaker) }} {{ .Title
    }}
    &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"delimiter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    {{ end }} {{ end }} {{ end }}
  &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  {{ end }}
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's obviously a lot more to the site than what I cover here so I encourage you, if you are curious, to explore the &lt;a href="https://github.com/remotesynth/certifiedfreshevents"&gt;repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As an aside, if I were to build it today, I would probably use &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt;, but there's really no specific issue with Hugo, I just am more comfortable in Astro nowadays. That being said, there's no compelling reason to change (other than it might be fun).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The site is deployed to &lt;a href="https://netlify.com"&gt;Netlify&lt;/a&gt;. There's really nothing special to share about that since Netlify makes the deployment super easy. A quirk of Hugo on Netlify means that I do need to specify the Hugo version as an environment variable but otherwise the deployment just works. One specific requirement of my site is to override the default Hugo build method to include the &lt;code&gt;-F&lt;/code&gt; or &lt;code&gt;--buildFuture&lt;/code&gt; flag because my events are future dated and, by default, Hugo ignores them. This flag means it will build items with dates in the future. I do also use some additional Netlify services, which I'll discuss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Functionality with Netlify Functions
&lt;/h3&gt;

&lt;p&gt;Many of the sites dynamic functionalities come from some of the embedded services, which I'll discuss more below. However, there are a few elements that require the use of a backend serverless functions via &lt;a href="https://www.netlify.com/platform/core/functions/"&gt;Netlify functions&lt;/a&gt;. All of mine are pretty simple, sign up for the mailing list, perform a search or sign up for an event via a signup form.&lt;/p&gt;

&lt;p&gt;All three of these are essentially performing a backend API call to external services (which I will discuss in more detail later). Netlify functions just make it easy for me to include the code within my web site's repo and they get deployed as serverless functions for me. For example, here is the Mailjet function code that uses the Node Mailjet SDK to post a new contact to the list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Mailjet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-mailjet&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;mailjet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Mailjet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAILJET_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAILJET_SECRET_KEY&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;listID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10244548&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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="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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email query parameter required&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&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;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&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;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email query parameter required&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="nx"&gt;mailjet&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contact&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="nf"&gt;request&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="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;IsExcludedFromCampaigns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lastName&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="k"&gt;async &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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addedToList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailjet&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contact&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="nf"&gt;id&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;managecontactslists&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="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;ContactsLists&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;ListID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addnoforce&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="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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good news! You've been added.&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="na"&gt;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;errorMsg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll dig into the search one in a moment, but the sign up for an event form doesn't interact directly with event service that I use as it doesn't have an API. However it does have a Zapier integration so I am posting to a Zapier webhook which then manages the post to Crowdcast (my event service).&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing Search with Algolia
&lt;/h3&gt;

&lt;p&gt;Search is an important (if underused) aspect of a site that contains about &lt;a href="https://cfe.dev/sessions/"&gt;300 free archive recordings&lt;/a&gt;. To implement search, I use &lt;a href="https://www.algolia.com/"&gt;Algolia&lt;/a&gt; which makes the search effective and easy. There are three aspects to the search implementation given that this is a static site. The first is posting new items as they are created.&lt;/p&gt;

&lt;p&gt;The posting of new events is handled through an npm package that is old but still works (I've been using it for years) called &lt;a href="https://www.npmjs.com/package/atomic-algolia"&gt;Algolia Atomic&lt;/a&gt;. It handles batching the requests so that you use the smallest number of operations against your Algolia account (honestly, Algolia's free plan is very generous in my opinion).&lt;/p&gt;

&lt;p&gt;To make this work with Hugo, I need to create a JSON file that lists all my sessions (currently, the search searches sessions and talk shows and not the upcoming events). First, I need to add an output to my Hugo config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;home&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;HTML&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RSS&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Algolia&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I need to define the output format as JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;outputFormats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Algolia&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;baseName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;algolia&lt;/span&gt;
    &lt;span class="na"&gt;isPlainText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;mediaType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
    &lt;span class="na"&gt;notAlternative&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in my default layouts I include a &lt;code&gt;list.algolia.json&lt;/code&gt; template that outputs the JSON necessary for this to work. It's kind of complicated to parse because it needs to pull all the pieces together from the multiple page parts we discussed above but it works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;&lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;Generates&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt; &lt;span class="nv"&gt;valid&lt;/span&gt; &lt;span class="nv"&gt;Algolia&lt;/span&gt; &lt;span class="nv"&gt;search&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Scratch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Add&lt;/span&gt; &lt;span class="s2"&gt;"index"&lt;/span&gt; &lt;span class="nv"&gt;slice&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;range&lt;/span&gt; &lt;span class="nv"&gt;where&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;RegularPages&lt;/span&gt; &lt;span class="s2"&gt;"Section"&lt;/span&gt; &lt;span class="s2"&gt;"in"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;slice&lt;/span&gt; &lt;span class="s2"&gt;"sessions"&lt;/span&gt; &lt;span class="s2"&gt;"talkshows"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="s2"&gt;".Params.recordings"&lt;/span&gt; &lt;span class="s2"&gt;"!="&lt;/span&gt; &lt;span class="nv"&gt;nil&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;not&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Draft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;not&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;private&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;isset&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Params&lt;/span&gt; &lt;span class="s2"&gt;"speakers"&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$eventArr&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;slice&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;TranslationBaseName&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$events&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;where&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;RegularPages&lt;/span&gt; &lt;span class="s2"&gt;".Params.sessions"&lt;/span&gt; &lt;span class="s2"&gt;"intersect"&lt;/span&gt; &lt;span class="nv"&gt;$eventArr&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="nv"&gt;$events&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$eventdate&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Format&lt;/span&gt; &lt;span class="s2"&gt;"Jan 2, 2006"&lt;/span&gt; &lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$speakerPage&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;index&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;first&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;speakers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$speaker&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;GetPage&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"/speakers/"&lt;/span&gt; &lt;span class="nv"&gt;$speakerPage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$homepageBanner&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;isset&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Params&lt;/span&gt; &lt;span class="s2"&gt;"homepage_banner"&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
      &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$homepageBanner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;homepage_banner&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
      &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$homepageBanner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Homepage_banner&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;end&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
    &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Scratch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Add&lt;/span&gt; &lt;span class="s2"&gt;"index"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;dict&lt;/span&gt; &lt;span class="s2"&gt;"objectID"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;UniqueID&lt;/span&gt; &lt;span class="s2"&gt;"date"&lt;/span&gt; &lt;span class="nv"&gt;$eventdate&lt;/span&gt; &lt;span class="s2"&gt;"dir"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Dir&lt;/span&gt; &lt;span class="s2"&gt;"expirydate"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ExpiryDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Unix&lt;/span&gt; &lt;span class="s2"&gt;"fuzzywordcount"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;FuzzyWordCount&lt;/span&gt; &lt;span class="s2"&gt;"keywords"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Keywords&lt;/span&gt; &lt;span class="s2"&gt;"kind"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Kind&lt;/span&gt; &lt;span class="s2"&gt;"lang"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Lang&lt;/span&gt; &lt;span class="s2"&gt;"lastmod"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Lastmod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Unix&lt;/span&gt; &lt;span class="s2"&gt;"permalink"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Permalink&lt;/span&gt; &lt;span class="s2"&gt;"publishdate"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;PublishDate&lt;/span&gt; &lt;span class="s2"&gt;"readingtime"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ReadingTime&lt;/span&gt; &lt;span class="s2"&gt;"relpermalink"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;RelPermalink&lt;/span&gt; &lt;span class="s2"&gt;"summary"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Summary&lt;/span&gt; &lt;span class="s2"&gt;"title"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Title&lt;/span&gt; &lt;span class="s2"&gt;"type"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Type&lt;/span&gt; &lt;span class="s2"&gt;"url"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Permalink&lt;/span&gt; &lt;span class="s2"&gt;"section"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Section&lt;/span&gt; &lt;span class="s2"&gt;"categories"&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Categories&lt;/span&gt; &lt;span class="s2"&gt;"banner"&lt;/span&gt; &lt;span class="nv"&gt;$homepageBanner&lt;/span&gt; &lt;span class="s2"&gt;"speaker"&lt;/span&gt; &lt;span class="nv"&gt;$speaker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;end&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
  &lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;end&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;end&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Scratch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;Get&lt;/span&gt; &lt;span class="s2"&gt;"index"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;jsonify&lt;/span&gt; &lt;span class="nv"&gt;-&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use this, there are two simple steps. First I run a local build of the site with the flag to build future dated posts and then I run the Algolia Atomic command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hugo &lt;span class="nt"&gt;-F&lt;/span&gt;
npm run algolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The latter command just aliases the &lt;code&gt;atomic-algolia&lt;/code&gt; call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming the Events
&lt;/h2&gt;

&lt;p&gt;We've mostly hit the part of this post where we leave code behind. A lot of CFE.dev runs on external services, the most important being the ones that run our live events. For that we use two services: &lt;a href="https://www.crowdcast.io/"&gt;Crowdcast&lt;/a&gt; and YouTube.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;As an aside, we used to use Vimeo rather than YouTube because it made it easy to make my videos only accessible on my own site. This was back when I had implemented a sign up for the site. The plan was maybe to try some subscription and/or drive more mailing list subscriptions, but ultimately I gave up on the sign ups and made everything free and open and, thus, dropped Vimeo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All of our major events (meetups and conferences) are done via Crowdcast. I originally chose this because it made it extremely easy to potentially charge for events without needing to code a registration and payment process. Nowadays, I don't charge for any of the events, but Crowdcast still works well for my purposes, with Q&amp;amp;A, chat, and other integrated features. I also use the embed to allow easy registration on my event pages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Onqd3e3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1i0w2i4n264vhaha519.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Onqd3e3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1i0w2i4n264vhaha519.png" alt="Crowdcast interface" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why use Crowdcast at all and why not move entirely to YouTube? The mailing list. Crowdcast lets me collect emails of registrants and add them to my mailing list whereas YouTube offers no such ability (afaik). And the mailing list drives a significant portion of my future registrations.&lt;/p&gt;

&lt;p&gt;The talk shows and live coding shows (&lt;a href="https://cfe.dev/talkshow/devrelish/"&gt;DevRel(ish)&lt;/a&gt;, &lt;a href="https://cfe.dev/talkshow/2full2stack/"&gt;2 Full 2 Stack&lt;/a&gt;) all run just on YouTube. The live stream is embedded in the site.&lt;/p&gt;

&lt;p&gt;The embed URLs for Crowdcast and/or YouTube (Crowdcast videos are eventually posted to YouTube as well) are just included as part of the page front matter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;recordings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;crowdcast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://crowdcast.io/c/building-an-api&lt;/span&gt;
    &lt;span class="na"&gt;start_time&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;04:47"&lt;/span&gt;
  &lt;span class="na"&gt;youtube&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://youtu.be/6kpvLQmDZls&lt;/span&gt;
    &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;57:30"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Supporting Tools
&lt;/h2&gt;

&lt;p&gt;There are a number of additional supporting tools and services that make the site run (if you're wondering, yes, it all adds up 💰💰).&lt;/p&gt;

&lt;h3&gt;
  
  
  Triggering Actions with Zapier
&lt;/h3&gt;

&lt;p&gt;The primary need for &lt;a href="https://zapier.com/app/dashboard"&gt;Zapier&lt;/a&gt; on this site is because Crowdcast does not currently provide an API. In order to sign folks up for the mailing list when they register for an event, I use a zap that passes the registration information to Mailjet.&lt;/p&gt;

&lt;p&gt;However, I also have a few other zaps. One I mentioned earlier is a webhook to post registrations from a form to Crowdcast. Another posts new events from my &lt;a href="https://cfe.dev/rss.xml"&gt;RSS feed&lt;/a&gt; to our &lt;a href="https://discord.gg/jthCg8DHSg"&gt;Discord&lt;/a&gt;. Finally, because events are very date-based, I have a Zap which triggers a daily Netlify rebuild to refresh dates and lists of upcoming events. This last one I could do with Netlify's &lt;a href="https://docs.netlify.com/functions/scheduled-functions/"&gt;scheduled functions&lt;/a&gt;, but that wasn't available when I built the site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Mailing Lists with Mailjet
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://mailjet.com/"&gt;MailJet&lt;/a&gt; is a recent addition to the site. Mailing lists can get &lt;em&gt;very&lt;/em&gt; expensive. In fact, the mailing list was by far the single most expensive item on this list but Mailjet helped with that. I started with Mailchimp, but they made pricing changes that made it incredibly expensive (for example, charging you for folks you can't communicate with because they unsubscribed). I then went to ActiveCampaign, which I used for years. As my mailing list grew though, the price became unsustainable for me. It includes a ton of automation and CRM-type features that I never used anyway.&lt;/p&gt;

&lt;p&gt;Mailjet is, in my opinion, a more stripped down emailing service, but it does everything I need it to do and saved me upwards of $200/mo ($50/mo vs $250+/mo).&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming via Streamyard
&lt;/h3&gt;

&lt;p&gt;I originally signed up for &lt;a href="https://streamyard.com/"&gt;Streamyard&lt;/a&gt; because the streaming options in Crowdcast v1's built in streaming were very limited. This has since improved enormously, but Streamyard has a number of other features that make it useful to me. The ability to do multi-streams is helpful as we've started streaming to both YouTube and Crowdcast at the same time. The customizable displays and branding are useful too. I'm sure I could do all this via OBS but I've often found it difficult and the benefit of Streamyard is that it makes it incredibly easy. I've also almost never had any technical issues with guests or screen sharing despite years of use and hundreds of guests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transcriptions with Otter
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://otter.ai/"&gt;Otter&lt;/a&gt; isn't entirely designed for what I use it for, which is to generate transcripts of sessions that are then posted to the site. It's really intended for transcribing work meetings live. It does not have a way (that I am aware of) to hook into Streamyard or Crowdcast, so I use it very manually. I download the audio from Streamyard of each event (it can handle video but audio-only makes the whole process faster to upload and transcribe).&lt;/p&gt;

&lt;p&gt;I then manually go through and make corrections and label speakers (it can auto-detect speakers who you've transcribed before but my speakers tend to be new each time). The good news is that it is pretty accurate, but this is still a time consuming process. I then export the transcript as text to the clipboard, paste it into a file in my transcripts folder in my Hugo site (I name the file the same as the session it transcribed to make it easy to find and combine). Finally, I make some Markdown formatting to make it easier to read (mostly just bolding names).&lt;/p&gt;

&lt;p&gt;Is this a lot of work for what is probably a lightly used feature? Yes. I think it can be valuable to some people and make the site more accessible but my hope is that it also helps with SEO (though I can't back that up with data).&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Shorts with Opus Clip
&lt;/h3&gt;

&lt;p&gt;This is another recent addition to my toolset. I have been toying with reusing the content on my site by creating short clips of the live episodes to reuse as YouTube Shorts and hopefully build my audience. The problem is that generating those 60 second clips is an extremely time-consuming process when done manually. Luckily, &lt;a href="https://www.opus.pro/"&gt;Opus Clip&lt;/a&gt; does this all via artificial intelligence.&lt;/p&gt;

&lt;p&gt;The nice things about Opus Clip versus competitors that I looked into were that it allowed you to edit the generated video and that it gave you a ton of videos each with an explanation as to why it thinks they might drive views (and scores them to help you decide).&lt;/p&gt;

&lt;p&gt;![Opus Clip]((&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwmy29e5nv9m2hemlxvs.png"&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwmy29e5nv9m2hemlxvs.png&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Has this driven YouTube subscriptions? I don't think so honestly. This could be that I haven't used it consistently enough. Some of the videos have gotten views but few have driven subs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payments via Stripe
&lt;/h3&gt;

&lt;p&gt;All of the above costs money. Once upon a time, I lost money every month keeping this site going. Thankfully sponsors have stepped in to help defray the costs of the site and allow me not only to keep creating content on there but to make all of it free. To handle invoicing for sponsors, I use Stripe. There's really nothing special to how I use it since all the invoicing is manually set up in the Stripe dashboard, but it's still a critical part of the services I use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Whew! 😅
&lt;/h2&gt;

&lt;p&gt;Having written all of this out, there's &lt;em&gt;a lot&lt;/em&gt; to the site. It's not complicated, just a lot of small pieces. Are there places where I could improve things or consolidate services? My assumption is yes, but the least available resource this site has isn't money but time. Keeping the site running and creating new content is extremely time consuming to the point that updates to the site tend to fall behind schedule. I do my best. 🤷&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>6 Years and 180 (Virtual) Events Later...</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Sun, 20 Aug 2023 23:36:50 +0000</pubDate>
      <link>https://dev.to/remotesynth/6-years-and-180-virtual-events-later-2ngh</link>
      <guid>https://dev.to/remotesynth/6-years-and-180-virtual-events-later-2ngh</guid>
      <description>&lt;p&gt;Back in 2017, most folks were skeptical when it came to virtual events. This included me.&lt;/p&gt;

&lt;p&gt;I had been attending IRL (in-real life) developer conferences as a speaker and attendee many years at that point, both in the context of my job in DevRel or prior. I'd even run a long list of IRL conferences including ones in Boston, New York City and even Sofia, Bulgaria. I have a passion for learning and for connecting with the developer community, and conferences provided an outlet for both. I was skeptical that virtual events could provide that.&lt;/p&gt;

&lt;p&gt;It was in that context that I orginally created &lt;a href="https://cfe.dev"&gt;Certified Fresh Events&lt;/a&gt; (or &lt;a href="https://cfe.dev"&gt;CFE.dev&lt;/a&gt;, as I now typically refer to it) this week in 2017. It was going to be an outlet to run developer events – outside of my work responsibilities – in person. And yet, here I am about to run my 180th virtual event on Tuesday. How'd that happen? How'd I go from being a virtual event skeptic to a a virtual event veteran?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On a side note, I am super excited about Tuesday's talk not just because it is the 6 year anniversary but also because it's on a really important topic for developers: &lt;a href="https://cfe.dev/events/frontend-with-intention/"&gt;Building Your Front-End with Intention&lt;/a&gt; where Chad Stewart will show us create a front-end architecture with intention by leveraging component-driven design. Join us at 1pm ET (UTC -4).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Necessity is the mother of virtual events...or something like that
&lt;/h2&gt;

&lt;p&gt;IRL events are expensive. I don't just mean they are expensive to attend (though they are, especially once you add in flights, meals and hotel). I mean that they take a large amount of investment up-front. There's usually a substantial venue deposit and equipment costs and speaker travel and marketing costs and so on. Even for someone who has run a lot of developer events, it's a risky investment.&lt;/p&gt;

&lt;p&gt;So I decided to build awareness of my new conference venture by running some virtual events. Having been in the industry for a long time and having participated in and organized a lot of events, I knew a lot of great speakers. My thought was, I could do a handful of virtual events, build a mailing list and then use that to launch my IRL events.&lt;/p&gt;

&lt;p&gt;But the virtual events exceeded my expectations. It wasn't just that my first event in on August 23, 2017 had 250+ live attendees, it was that I found that I could scratch that itch around learning and developer community nearly as well with a virtual event than with a IRL event. Sure, chat was no replacement for the "hallway track", but there were a ton of benefits to virtual events:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They were low-risk. I had to pay for a platform to run the events (I've been on Crowdcast for the entire 6 years), but there were no other significant costs to get started. This meant I could run as many of them as I wanted for, more or less, a fixed cost.&lt;/li&gt;
&lt;li&gt;They were free. Because of the low risk, I could make them freely available to attend.&lt;/li&gt;
&lt;li&gt;They were global. My speakers could come from anywhere around the world, as did my attendees.&lt;/li&gt;
&lt;li&gt;They were recorded. Recording IRL events can get costly, so many organizers don't do it. Because my events were virtual, they were always recorded.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't get me wrong. I didn't come to believe virtual events were a IRL event replacement, but I did come to see that they had great value.&lt;/p&gt;

&lt;h2&gt;
  
  
  6 years later...
&lt;/h2&gt;

&lt;p&gt;One thing Covid shutdowns did was convince many people that there is value in virtual events. Sure, many of us miss that in-person interaction, but there's room for both. I've increased the amount of programming and special events that I run on CFE.dev, so there's defintely a lot more going on now that there was 6 years ago, but we've amassed some crazy stats over those 6 years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;180+ events&lt;/li&gt;
&lt;li&gt;230+ presentations&lt;/li&gt;
&lt;li&gt;Almost 200 speakers&lt;/li&gt;
&lt;li&gt;240+ recordings&lt;/li&gt;
&lt;li&gt;25+ interviews&lt;/li&gt;
&lt;li&gt;7 virtual conferences&lt;/li&gt;
&lt;li&gt;8 workshops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And in recent years, every single one of these events is free, including the entire archive of the last 6 years. If &lt;a href="https://cfe.dev"&gt;CFE.dev&lt;/a&gt; played a helpful role in your developer career, I'd love to hear from you and I hope you'll continue to be a part of the community. If you've never heard of the site or been to one of our events, now's the best time. We've got &lt;a href="https://cfe.dev/events/"&gt;so much going on&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;P.S. If your company is interested in sponsoring CFE to help me keep offering free developer events, you can find our &lt;a href="https://cfe.dev/cfe-prospectus.pdf"&gt;prospectus here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>career</category>
      <category>community</category>
    </item>
    <item>
      <title>Adding a Mailing List Subscription with Mailjet and Netlify Functions</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Sun, 13 Aug 2023 19:27:52 +0000</pubDate>
      <link>https://dev.to/remotesynth/adding-a-mailing-list-subscription-with-mailjet-and-netlify-functions-986</link>
      <guid>https://dev.to/remotesynth/adding-a-mailing-list-subscription-with-mailjet-and-netlify-functions-986</guid>
      <description>&lt;p&gt;Next week is the 6 year anniversary of &lt;a href="https://cfe.dev"&gt;CFE.dev&lt;/a&gt;. I probably wouldn't have believed you if you told me when I got started 6 years ago that the single biggest expense to running a site like this would be maintaining a mailing list. That's right, it's not the streaming service (Streamyard) or the virtual event service (Crowdcast) or the hosting (Netlify), it's emails.&lt;/p&gt;

&lt;p&gt;I moved from Mailchimp some years back because the cost was going to be a major burden, especially once they changed how they calculate their pricing (anyone you've been in contact with, even if they are unsubscribed and you aren't allowed to communicate with them, counts against your cap). At the time, ActiveCampaign was a cheaper but still full featured option. However, I never really needed all the features (particularly around campaign automation) and it's come time again where I need to move. As it turns out, moving from ActiveCampaign to &lt;a href="https://mailjet.com"&gt;Mailjet&lt;/a&gt; gets me the features I need for over $100 less per month.&lt;/p&gt;

&lt;p&gt;There are two ways you get subscribed to my mailing list. The first is through an automation built with Zapier that adds you when you register for an event via Crowdcast. The second is via the subscription forms on my site. In this post, I just want to share the code for adding a new contact to Mailjet via a Netlify Function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Netlify Function
&lt;/h2&gt;

&lt;p&gt;Mailjet provides a &lt;a href="https://github.com/mailjet/mailjet-apiv3-nodejs"&gt;JavaScript SDK&lt;/a&gt; that actually makes integrating with its API pretty easy. You'll need your Mailjet API key and secret key, which you can find in Mailjet under Account Settings and then choose "API Key Management (Primary and Sub-account)" under REST API. Place those in an environment variable.&lt;/p&gt;

&lt;p&gt;One quirk (to me) of Mailjet is that you can have contacts but if they aren't added to a list, then you can't send them campaigns. In order to add them to a list, you'll need the list ID, which you can find under Contacts  and then Contact Lists in your account. I didn't put this in an environment variable, though I could have. It's not sensitive info but that might make it easier to manage. However, it's worth noting that you can have multiple lists and may want to add the user to more than one.&lt;/p&gt;

&lt;p&gt;As you'll see in the code below, there's really two steps to the process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the user to your contacts and get their new user ID.&lt;/li&gt;
&lt;li&gt;Add the user to the appropriate list(s).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Outside of that, the code is pretty simple. Is it weird that I use a &lt;code&gt;then&lt;/code&gt; and then an &lt;code&gt;await&lt;/code&gt; within the result? Maybe, but it works and it was easier to wrap my head around.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Mailjet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-mailjet&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;mailjet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Mailjet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAILJET_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAILJET_SECRET_KEY&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;listID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12345678&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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="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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email query parameter required&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&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;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&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;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email query parameter required&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="nx"&gt;mailjet&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contact&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="nf"&gt;request&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="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;IsExcludedFromCampaigns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;lastName&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="k"&gt;async &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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addedToList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailjet&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contact&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="nf"&gt;id&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;managecontactslists&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="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;ContactsLists&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;ListID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addnoforce&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="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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good news! You've been added.&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="na"&gt;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;errorMsg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;statusCode&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to note is that I am returning the error message from Mailjet if the user wasn't added to the list, but, unlike ActiveCampaign, it's not the most user friendly message. Typically, this is if you are already on the list, but it might be worth checking the messages and responding with something more friendly (it's on my todo list).&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Function
&lt;/h2&gt;

&lt;p&gt;So, CFE is a totally static site, so I need to call this function via client JavaScript. IKR! You can still do that?!? &lt;/p&gt;

&lt;p&gt;Every Netlify function has a URL that you can post to, so it's just a matter of making the HTTP request. The script is pretty simple, it adds a submit event listener to the form, gathers the data and then uses &lt;a href="https://axios-http.com/"&gt;Axios&lt;/a&gt; to make the request to the function. Do I need Axios? No, but it works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;registerForm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&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;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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;join-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;join-email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;errorMessage&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;email&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="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please fill in all fields&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;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameArray&lt;/span&gt; &lt;span class="o"&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;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &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;nameArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nameArray&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nameArray&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;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;nameArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nameArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="nx"&gt;axios&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/.netlify/functions/mailjet&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;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lastName&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="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;response&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="nx"&gt;errorMsg&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;errorMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&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="nx"&gt;errorMsg&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;response&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="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;errorMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&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="nx"&gt;msg&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="nf"&gt;function &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="k"&gt;if &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="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;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The request failed. Please try again in a moment.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to login user: %o&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="k"&gt;throw&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  That's it!
&lt;/h2&gt;

&lt;p&gt;So there's nothing groundbreaking here and there are definitely ways to improve the code (this is a side project, so often "it works" is all I have time for). But if you are running a mailing list and looking to use Mailjet, hopefully this code will help.&lt;/p&gt;

&lt;p&gt;P.S. If you notice any issues with the new mailing list form or with the mailing list itself (assuming you're a subscriber), let me know.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Lifting Off with Astro 🚀</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Mon, 07 Aug 2023 12:24:55 +0000</pubDate>
      <link>https://dev.to/remotesynth/lifting-off-with-astro-4omm</link>
      <guid>https://dev.to/remotesynth/lifting-off-with-astro-4omm</guid>
      <description>&lt;p&gt;&lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; is still a relatively new JavaScript framework, having been around since 2021, but, in my opinion, it addresses two major issues that face developers doing full stack JavaScript: the cost of that JavaScript and the forces of inertia. Let's discuss the problems first and then look at how Astro addresses them. Finally, we'll explore an example of an Astro application to help you get started using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost of JavaScript
&lt;/h2&gt;

&lt;p&gt;Every year since 2018, Addy Osman writes or speaks about &lt;a href="https://www.youtube.com/watch?v=ZKH3DLT4BKw" rel="noopener noreferrer"&gt;The Cost of JavaScript&lt;/a&gt;. JavaScript remains the most expensive resource on your site and has been the same since he first talked about it in 2018. That's because it's not just about the weight of the resources, which continues to grow, but also the time it takes to process it.&lt;/p&gt;

&lt;p&gt;This past year saw a smaller than usual increase in the size of JavaScript, but it was still up 10% according to the Web Almanac.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;From 2021 to 2022, an increase of 8% [in the amount of JavaScript shipped to browsers] for mobile devices was observed, whereas desktop devices saw an increase of 10%.&lt;/strong&gt; ...The fact remains that &lt;strong&gt;more JavaScript equates to more strain on a device’s resources&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://almanac.httparchive.org/en/2022/javascript" rel="noopener noreferrer"&gt;Web Almanac&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The raw numbers can be shocking, with sites at p90 loading about 1.5MB of JavaScript. I'll hazard a guess that these same sites will have lots of blocking scripts, meaning the site is unusable while the user waits for the JavaScript to process. But, even worse, much of this JavaScript is unused.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Unnecessary JavaScript
&lt;/h3&gt;

&lt;p&gt;Unused JavaScript is obviously wasteful, and should be addressed, but, in my view, nearly as wasteful is a category of JavaScript that I am calling "unnecessary JavaScript". This is JavaScript that is used but ultimately isn't needed because it does things that could have been done without JavaScript by just using the web platform.&lt;/p&gt;

&lt;p&gt;The rise of unnecessary JavaScript is related to the fact that at some point we also started using tools like React, that were meant to solve problems of complex and interactive application development, to build everything including sites that were heavy on content or basic application functionality with limited interactivity.&lt;/p&gt;

&lt;p&gt;For example, let's imagine a SaaS company site. There is probably a public facing portion of the site that is mostly marketing with perhaps some forms and simple interactions. Meanwhile there is likely a console of some sort that is fairly complex and represents the core of how the customer interacts with the SaaS application. The needs of the console probably make the cost, in terms of JavaScript, necessary enough to warrant a framework like React. The needs of the public facing site...yeah, not so much.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftgo3oe509jrznwujxzrt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftgo3oe509jrznwujxzrt.jpg" alt="are you sure these are the right tools for the job"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think a big reason why developers continue to load on more JavaScript and use frameworks like React for things the framework isn't really designed for is because we've built up years of dependency that makes it increasingly difficult to move without staring down a steep learning curve and/or starting from scratch.&lt;/p&gt;

&lt;p&gt;But the good news is that new tools, like Astro are beginning to address these issues.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;New front-end frameworks like Solid and Qwik are suggesting that &lt;strong&gt;React might not have all the answers after all&lt;/strong&gt;, and on the server &lt;strong&gt;Astro&lt;/strong&gt;, Remix and Next.js (among others) are &lt;strong&gt;making us reconsider how much code we really need to ship to the client&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://2022.stateofjs.com/en-US/" rel="noopener noreferrer"&gt;State of JavaScript 2022&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Benefits of Astro
&lt;/h2&gt;

&lt;p&gt;Astro addresses the two big issues I talked about above: unnecessary JavaScript; and the difficulty of transitioning. Let's explore how by first diving into how it deals with unnecessary JavaScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Islands
&lt;/h3&gt;

&lt;p&gt;By default, Astro delivers 0kb of JavaScript to the client. Of course, very few sites today can function with zero JavaScript, but Astro helps you eliminate unnecessary JavaScript via something called the "islands architecture."&lt;/p&gt;

&lt;p&gt;First coined by Etsy's frontend architect Katie Sylor-Miller in 2019 as the "Component Islands" pattern, the idea behind an islands architecture is to only send the JavaScript for components that require client-side hydration or interactivity. Here's how it is described from the canonical article on it by Jason Miller:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The general idea of an “Islands” architecture is deceptively simple: &lt;strong&gt;render HTML pages on the server, and inject placeholders or slots around highly dynamic regions.&lt;/strong&gt; These placeholders/slots contain the server-rendered HTML output from their corresponding widget. They denote regions that can then be "hydrated" on the client into small self-contained widgets, reusing their server-rendered initial HTML.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jasonformat.com/islands-architecture/" rel="noopener noreferrer"&gt;Islands Architecture&lt;/a&gt; by Jason Miller&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This leads to a page that might look like the below diagram wherein the header and image carousel require JavaScript but the remainder of the page does not. The typical single page application (SPA) architecture would hydrate the entire page using JavaScript. But the islands architecture proposes that by sending only the JavaScript required for rendering these components, we can ultimately minimize the amount of JavaScript necessary on the client.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  How islands work in Astro
&lt;/h4&gt;

&lt;p&gt;So how does this work? In the below example (borrowed from the Astro docs), the Astro component (don't worry, we'll talk about the different types of components in a bit) requires no JavaScript to render but the React component does and thus has the directive for &lt;code&gt;client: load&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// Example: Use a dynamic React component on the page.
import MyAstroComponent from '../components/MyAstroComponent.astro';
import MyReactComponent from '../components/MyReactComponent.jsx';
---
&lt;span class="c"&gt;&amp;lt;!-- This component is now interactive on the page! --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MyReactComponent&lt;/span&gt; &lt;span class="na"&gt;client:load&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- This component loads zero js ---&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;MyAstroComponent&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Astro actually offers a ton of flexibility with its hydration directives that allow you to set the appropriate priority for each component all the way to forcing full client-side rendering where necessary.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;load&lt;/strong&gt; – high priority. Elements that will be immediately visible on the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;idle&lt;/strong&gt; – medium priority. Elements that do not need to be immediately interactive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;visible&lt;/strong&gt; – low priority. Typically below the fold elements that become active when in the viewport.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;media&lt;/strong&gt; – low priority. Elements that might only be visible on certain screen sizes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;only&lt;/strong&gt; – skip server rendering entirely and only render on the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So to summarize, Astro will send zero JavaScript by default but allows you to designate a component "island" that requires JavaScript to render or otherwise enable interactivity. However, you have fine-grained control over how that JavaScript is loaded to help ensure the best performance for your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components in Astro
&lt;/h3&gt;

&lt;p&gt;So we've talked about how Astro reduces JavaScript, but how does it ease transitioning from an existing SPA architecture? It does this by supporting a variety types of components, including components built in other frameworks like React, Vue, Svelte and more. This eases the learning curve but also means that developers don't necessarily need to toss all of their existing code as components can be reused or shared within the Astro site.&lt;/p&gt;

&lt;p&gt;You can even mix and match components from different frameworks. I'm not sure I'd recommend this but you can do it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
// Example: Mixing multiple framework components on the same page.
import MyReactComponent from '../components/MyReactComponent.jsx';
import MySvelteComponent from '../components/MySvelteComponent.svelte';
import MyVueComponent from '../components/MyVueComponent.vue';
---
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MySvelteComponent&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MyReactComponent&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MyVueComponent&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Astro also offers their own component syntax for an Astro component (&lt;code&gt;.astro&lt;/code&gt;). These are HTML only components on the client, though you can include JavaScript via &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags or run JavaScript on the server. As we'll see, you can still even build full sites and even simple web applications using just Astro components.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Astro
&lt;/h2&gt;

&lt;p&gt;All of the above may sound amazing and you may be feeling like, "Build all the things with Astro!" But, in truth, Astro isn't designed to be the solution to everything. Their docs make this clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Astro was designed for building content-rich websites.&lt;/strong&gt; This includes most marketing sites, publishing sites, documentation sites, blogs, portfolios, and some ecommerce sites.&lt;/p&gt;

&lt;p&gt;By contrast, most modern web frameworks are designed for building web applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.astro.build/en/concepts/why-astro/" rel="noopener noreferrer"&gt;Why Astro?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This makes sense but I'll admit that the dividing line is fuzzy. Can you build applications using Astro? Most definitely. Just as you could build a blog with Next.js (something it feels like serious overkill for), you could likely build (to use my prior example) a SaaS application dashboard with Astro, but it wouldn't be the best tool for the job.&lt;/p&gt;

&lt;p&gt;Ultimately, that dividing line is up to you but it is worth pointing out that it's not all or nothing. You could move the more content-focused aspects of your site to Astro and leave the rest to something like Next.js. This would help you only use the necessary JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an Example Astro Application
&lt;/h2&gt;

&lt;p&gt;Let's explore an example I built using Astro. As someone who speaks frequently at conferences, I usually do a fairly poor job of tracking both the content of my CFP submissions and where I submitted them to. The application I built is fairly simple one that allows me to save my CFP titles and abstracts as well as which conferences I have submitted them to and whether they were accepted or not.&lt;/p&gt;

&lt;p&gt;You can find the &lt;a href="https://github.com/remotesynth/astro-cfp-tracker" rel="noopener noreferrer"&gt;repo here on GitHub&lt;/a&gt;. It currently doesn't support multiple users or any advanced functionality (plans for the future!) but it will help us cover the basics of getting started building apps with Astro. The backend uses &lt;a href="https://appwrite.io/" rel="noopener noreferrer"&gt;AppWrite Cloud&lt;/a&gt; but I did do another version porting the data portion to Postgres that you can &lt;a href="https://github.com/remotesynth/astro-demo-postgres" rel="noopener noreferrer"&gt;find here&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;This application uses no JavaScript, but still allows for dynamic output. It's a great example that, while React/Vue/Svelte components have their place, you can do a lot using just Astro components. Let's go through some of the basics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;The easiest way to get started is via some of the templates on &lt;a href="https://astro.new" rel="noopener noreferrer"&gt;Astro.new&lt;/a&gt;. In this case, I used the &lt;a href="https://astro.new/with-tailwindcss?on=github" rel="noopener noreferrer"&gt;Tailwind template&lt;/a&gt; which, as the name implies, integrates Tailwind. In Astro &lt;a href="https://astro.build/integrations/" rel="noopener noreferrer"&gt;integrations&lt;/a&gt; are handled like plugins that you install separately. This even includes things like adding support for deployment options like &lt;a href="https://docs.astro.build/en/guides/integrations-guide/netlify/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; for example.&lt;/p&gt;

&lt;p&gt;To generate a site using the Tailwind template, I just used the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create astro@latest &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--template&lt;/span&gt; with-tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will walk you through a command line based wizard that ultimately generates a set of project files using the default Astro folder structure. It will also, if you choose, install all the necessary dependencies.&lt;/p&gt;

&lt;p&gt;The key thing to note first is the Astro configuration file, &lt;code&gt;astro.config.mjs&lt;/code&gt;. Let's look at mine because I made a couple small tweaks to the one that was generated.&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;defineConfig&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;astro/config&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;tailwind&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;@astrojs/tailwind&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://astro.build/config&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;tailwind&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First off, I have removed the MDX plugin as my application doesn't use MDX, so the only integration is the Tailwind one. Second, I added the &lt;code&gt;output: server&lt;/code&gt; because my application is fully server-side rendered. By default, Astro outputs as static files but it also supports two other rendering modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;server&lt;/code&gt; which is fully server side rendered. Since my application, as it currently exists, isn't designed to pregenerate any pages as static (it's all effectively user generated content), SSR was the right choice.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hybrid&lt;/code&gt; which is a combination of server-side rendering and static rendering. This probably suits most sites as it allows pages like your about page, for example, or even static blog posts to opt-out of server-side rendering and be pre-rendered for even better performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating Layouts
&lt;/h3&gt;

&lt;p&gt;Layouts are typically placed in a &lt;code&gt;/src/layouts&lt;/code&gt; folder, but that's a convention not a requirement. Since my app doesn't have many different views, I only created one layout, &lt;code&gt;main.astro&lt;/code&gt;, which just serves as the shell of the application.&lt;/p&gt;

&lt;p&gt;The two key things to point out in the code below (note that for brevity I will be removing the Tailwind classes) is that you can pass props into an Astro component (in this case I am just passing an HTML title) and that you use the &lt;code&gt;&amp;lt;slot /&amp;gt;&lt;/code&gt; tag to indicate where the generated content should be output.&lt;/p&gt;

&lt;p&gt;You should also note that the JavaScript that runs on the server, either at build time for static rendering or runtime for SSR, is placed at the top of the file bracketed by &lt;code&gt;---&lt;/code&gt; as you might typical frontmatter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
const { title } = Astro.props;
---

&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/svg+xml"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/favicon.svg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{title}&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can then import and use a layout anywhere in my application or, in the case of Markdown or MDX, specify a layout to use in the frontmatter. You can also nest layouts, for example if I had this shell as the base and sub-layouts for different sections of my site.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;p&gt;As the UI is fairly simple, the sample application only uses a couple of simple Astro components. Let's look at the component that renders a session in the list page. It is very similar to the layout above in that it can do imports and run JavaScript on the server in the block at the top of the page. I am passing in the session as a prop which is then used to render the output between &lt;code&gt;{&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt;. Notice that I can run arbitrary JavaScript within these as I do in the date formatting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import Button from '../components/Button.astro';

const {session} = Astro.props;
---
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;`/sessions/${session['$id']}`&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{session.Title}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"16"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"16"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 16 16"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;Created: {new Date(session['$createdAt']).toDateString()}
        &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"16"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"16"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 16 16"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;Updated: {new Date(session['$updatedAt']).toDateString()}
        &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{session.Abstract}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;url=&lt;/span&gt;&lt;span class="s"&gt;{`/addSession?sessionID=${session['$id']}`}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Edit
        &lt;span class="nt"&gt;&amp;lt;/Button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;url=&lt;/span&gt;&lt;span class="s"&gt;{`/addCFP?sessionID=${session['$id']}`}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Add a CFP
        &lt;span class="nt"&gt;&amp;lt;/Button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the layout and component in a page works just as you would expect. You simply import them at the top of the file. You can pass in props via attributes to the tag. Notice that you can do things like output a list using &lt;code&gt;map()&lt;/code&gt; as you typically would in a React component. Also note that the server-side JavaScript supports top-level &lt;code&gt;await&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import BaseLayout from '../layouts/main.astro';
import CFPCard from '../components/CFPCard.astro';
import Button from '../components/Button.astro';
import {getSessions} from '../lib/AppWrite.js';

const sessions = await getSessions();
---

&lt;span class="nt"&gt;&amp;lt;BaseLayout&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"My CFP Submissions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-36 m-4 float-right"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;url=&lt;/span&gt;&lt;span class="s"&gt;"/addSession"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;New Session&lt;span class="nt"&gt;&amp;lt;/Button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-4 text-3xl font-bold leading-tight tracking-tighter text-gray-700 md:text-5xl dark:text-gray-300"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Current Sessions&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    {
        sessions.map((session) =&amp;gt; (&lt;span class="nt"&gt;&amp;lt;CFPCard&lt;/span&gt; &lt;span class="na"&gt;session=&lt;/span&gt;&lt;span class="s"&gt;{session}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;))
    }
&lt;span class="nt"&gt;&amp;lt;/BaseLayout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Routing
&lt;/h3&gt;

&lt;p&gt;As is a common convention in full-stack JavaScript frameworks, Astro supports file based routing but routing is different than you may be used to because Astro is what's now commonly referred to as an MPA or multi-page application. Essentially, instead of loading an app shell and then rehydrating the shell based upon the route, Astro renders a new page with a full request/response – just like we did in the old days (except back then they were just web sites and not MPAs). You can read more about &lt;a href="https://docs.astro.build/en/concepts/mpa-vs-spa/" rel="noopener noreferrer"&gt;MPAs vs SPAs&lt;/a&gt; in Astro's docs.&lt;/p&gt;

&lt;p&gt;The routing is based off the structure within the &lt;code&gt;/pages&lt;/code&gt; directory. This means that if there is an &lt;code&gt;/src/pages/about.astro&lt;/code&gt; that will resolve to route of &lt;code&gt;/about.html&lt;/code&gt;. Again, this doesn't cause the app to rehydrate but will do a full server request and response, whether or not that route is statically rendered or server-side rendered.&lt;/p&gt;

&lt;p&gt;Astro also supports dynamic routes as well as things like catchall routes. Here's some examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;about&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt; &lt;span class="c1"&gt;// renders /about&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;blog&lt;/span&gt;&lt;span class="o"&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;astro&lt;/span&gt; &lt;span class="c1"&gt;// renders /blog/post1 and /blog/post2&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;]&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="c1"&gt;// renders /api/v2/posts.json&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;astro&lt;/span&gt; &lt;span class="c1"&gt;// renders /about and /about/team&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are statically rendering a dynamic route, you do need to supply predefined paths for a route via a &lt;code&gt;getStaticPaths()&lt;/code&gt; method in the file. If you are server side rendering as I am, this isn't required. However, this means that someone could hit a route that doesn't exist, in which case you'll need to handle that.&lt;/p&gt;

&lt;p&gt;In the example app, I have one dynamic route that renders a page for any of the sessions that I submit. Because this is server side rendered, I do not need to predefine the paths, but I am returning a 404 response if the path doesn't exist (I could alternately use &lt;code&gt;Astro.redirect()&lt;/code&gt; to redirect them to a page instead).&lt;/p&gt;

&lt;p&gt;You'll also note that I am using a URL variable to update a CFP as accepted. This is possible because this page is server side rendered. Otherwise I would have to have used client-side JavaScript to manage the request and updating the page HTML to reflect the change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;BaseLayout&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;../../layouts/main.astro&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;Button&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/button.astro&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;getSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getCFPs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;acceptCFP&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/AppWrite.js&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;currentPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Astro&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cfpid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cfp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// if the URL variable for a CFP ID is present, we're accepting that session&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;cfpid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;acceptCFP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfpid&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;session&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;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&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="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not found&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;cfps&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;getCFPs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BaseLayout&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`Session: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&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="s2"&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="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;h2&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;session&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;/h2&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="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Created&lt;/span&gt;&lt;span class="p"&gt;:&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toDateString&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;/p&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;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Updated&lt;/span&gt;&lt;span class="p"&gt;:&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$updatedAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toDateString&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;/p&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="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="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;div&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;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Abstract&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;/div&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;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Submissions&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&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;ol&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;cfps&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;cfp&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;acceptLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;a href="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?cfp=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cfp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;Accepted?&amp;lt;/a&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&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;cfp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Conference&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Submitted&lt;/span&gt; &lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SubmissionDate&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cfp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Accepted&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Accepted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;acceptLink&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&amp;gt; }&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;li&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ol&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;Button&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;Back&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/BaseLayout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling form submissions
&lt;/h3&gt;

&lt;p&gt;The only other thing I want to point out in this demo app is that it's pretty easy to handle form submissions via standard HTML and server-side JavaScript using an Astro component. For example, let's look at the page that handles the form for adding a new session title and abstract.&lt;/p&gt;

&lt;p&gt;The form submission is handled by looking for the POST method and then getting the values from the submitted form data. The same form is used for updating an existing session by looking for an URL variable and populating the form values. If the form submission is successful, I am redirecting back to the session list on the home page using &lt;code&gt;Astro.redirect()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One thing worth noting is that there is no real error handling on the server side in this example. The form does use basic HTML form validation, but an improvement would be to ensure that there is some degree of server-side error handling as well. In addition, the &lt;code&gt;alert()&lt;/code&gt; method where I dump the error message if there is one is a server-side alert rather than a client-side one, so, as it exists today, the user wouldn't know if the server submission failed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;---&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;BaseLayout&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;../layouts/main.astro&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;createSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateSession&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/AppWrite.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// handle the form submission&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;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="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;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&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;sessionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sessionID&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;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;abstract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abstract&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;sessionID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionID&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="nx"&gt;abstract&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;await&lt;/span&gt; &lt;span class="nf"&gt;createSession&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="nx"&gt;abstract&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;Astro&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;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="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="nf"&gt;alert&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// if a URL variable is present, we are editing an existing session&lt;/span&gt;
&lt;span class="c1"&gt;// we can handle URL variables on the server because this is SSR&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sessionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sessionID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;title&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;abstract&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;sessionID&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;session&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;getSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionID&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;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;session&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="nx"&gt;abstract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Abstract&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="o"&gt;---&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BaseLayout&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Add/Edit a Session Abstract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="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;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;Add&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;Edit&lt;/span&gt; &lt;span class="nx"&gt;Session&lt;/span&gt; &lt;span class="nx"&gt;Abstract&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sessionID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sessionID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;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;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;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;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;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nx"&gt;Session&lt;/span&gt; &lt;span class="nx"&gt;Title&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&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="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;input&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;
                        &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&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;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="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="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="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="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;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;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;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;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Abstract&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&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="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;textarea&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abstract&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your text here..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;required&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;abstract&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;/textarea&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="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="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="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="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;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;a&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Cancel&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&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;/a&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w-full md:w-auto p-1.5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&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;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Save&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&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;/button&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="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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/BaseLayout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Prepare for your launch! 🚀
&lt;/h2&gt;

&lt;p&gt;I hope that this has given you all the foundations you need to get started with Astro and you are ready to take off! As I noted, the key benefits Astro offers of reduced JavaScript and better performance are going to be increasingly important as we try to deal with the JavaScript bloat that the SPA era has left behind. But Astro's support of existing framework components and relatively gentle learning curve will help you make the transition from a SPA framework. So, give Astro a try by going to &lt;a href="https://astro.new" rel="noopener noreferrer"&gt;Astro.new&lt;/a&gt; and starting a new project or digging into their extensive and well-written &lt;a href="https://docs.astro.build/en/getting-started/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Is Jamstack Officially Finished?</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Fri, 28 Jul 2023 12:59:41 +0000</pubDate>
      <link>https://dev.to/remotesynth/is-jamstack-officially-finished-50kb</link>
      <guid>https://dev.to/remotesynth/is-jamstack-officially-finished-50kb</guid>
      <description>&lt;p&gt;Earlier this week, Netlify officially killed The Jamstack Community Discord. It was a rather abrupt end, with little more than a week's notice. Here's a portion of the announcement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Over time, we have noticed a decline in activity and engagement within the server. After careful consideration, we have come to the decision to sunset the Jamstack Discord. First and foremost, this does &lt;strong&gt;not&lt;/strong&gt; mean the end of the Jamstack community. In fact, it is quite the opposite. We believe it's important to focus our energy and resources on areas where our community remains vibrant and active, like in the various projects and other communities out there doing wonderful things on the web. Sunsetting the Jamstack Discord server will allow us to concentrate our efforts on those spaces, fostering stronger connections and having more meaningful conversations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The post ended with a list of other communities folks could join, but, with only one exception, they were all tool specific Discord servers for Astro, Eleventy, Next, SolidJS and Svelte. (The exception was &lt;a href="https://slack.tnd.dev/"&gt;The New Dynamic slack community&lt;/a&gt; which, I seem to recall, has existed since before the term Jamstack and is still active.)&lt;/p&gt;

&lt;p&gt;More importantly, Netlify, the creator and stewards of the term Jamstack including it's &lt;a href="https://jamstack.org/"&gt;canonical site&lt;/a&gt;, has largely abandoned the term as well, in favor of a "composable web" term that better aligns with their ambitions around becoming a broader enterprise platform including content (with tools like Netlify Connect).&lt;/p&gt;

&lt;p&gt;So, despite the post ending the Discord notes that the Jamstack community is not dead, is it actually?&lt;/p&gt;

&lt;h2&gt;
  
  
  What was Jamstack in 2023?
&lt;/h2&gt;

&lt;p&gt;Every year for the past few years, I've written a post discussing what Jamstack is as we enter the year. This is because the definition has continued to shift to accommodate new tools, new services and new platform features. This past year, &lt;a href="https://remotesynthesis.com/blog/jamstack-in-2023/"&gt;I discussed&lt;/a&gt; how recent changes to the definition had made it more vague leading me to the conclusion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Jamstack has become more of a “community” than a set of architectural rules.&lt;/p&gt;

&lt;p&gt;I don’t say that to devalue it.&lt;/p&gt;

&lt;p&gt;This community builds things a thousand different ways but each of them have overlapping interests in a common sets of tools, services, strategies and architectures. I can read an article or &lt;a href="https://www.manning.com/books/the-jamstack-book"&gt;book about Jamstack&lt;/a&gt;, go to a &lt;a href="https://thejam.dev/"&gt;conference about Jamstack&lt;/a&gt;, join a meetup about Jamstack, get a &lt;a href="https://jamstack.email/"&gt;newsletter about Jamstack&lt;/a&gt; and I know that I will gain information that is applicable to what I am building. There’s huge value in that, even if the definition is vague and not prescriptive in any manner.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I made much the same argument on &lt;a href="https://shoptalkshow.com/554/"&gt;The Shop Talk Podcast&lt;/a&gt; in February.&lt;/p&gt;

&lt;p&gt;But does that conclusion stand up today? Perhaps not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What defines community?
&lt;/h2&gt;

&lt;p&gt;It's not that I am trying to overstate the importance of the Jamstack Discord. It is correct that activity and interaction had fallen off on that site (and spam had increased) ever since the community lost its full-time steward within Netlify. But this loss of a steward and subsequent inactivity were obviously symptomatic of larger problem and it is a problem that tool-specific Discord's cannot fix.&lt;/p&gt;

&lt;p&gt;The most relevant  &lt;a href="https://www.merriam-webster.com/dictionary/community"&gt;dictionary definition&lt;/a&gt; of community is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a body of persons of common and especially professional interests scattered through a larger society&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But what ultimately turns a disparate group of professionals (i.e. developers) into a community (i.e. Jamstack community) is communication and connection. Everyone working within their own silos, even if they share common interests, does not make a community. And right now there is no means remaining of connecting those folks in whatever was once a Jamstack community. The meetups are dead, the conference appears to be gone (no 2023 date has been announced) and now the Discord is gone.&lt;/p&gt;

&lt;p&gt;Yes, many of these same people may be on the tool-specific communities, but what made them part of a larger Jamstack community was the connections beyond each specific tool. As I said earlier this year:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There are thousands of permutations of the above and no two sites may have the same stack or architecture, but there are clearly a bunch of overlapping interests for the developers building these sites regardless. For example, you may be using Nuxt while I use Eleventy, but I can still learn about using Supabase from/with you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This kind of connection can't happen in an Astro or Nuxt or Eleventy specific community.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does this mean?
&lt;/h2&gt;

&lt;p&gt;Honestly, this is all ultimately has little impact on how developers do their jobs, whether they considered themselves Jamstack developers or not. But when it comes to those connections, it probably means a deepening siloing of developers around their specific tools. It likely also means that things like the books, events and other resources that tried to target the broader community across these tools are unlikely to continue as developers no longer use or identify with the term Jamstack.&lt;/p&gt;

&lt;p&gt;I, myself, will likely retire using the term anymore, even as I don't have a replacement. I do think newsletters like &lt;a href="https://jamstack.email/"&gt;Jamstacked&lt;/a&gt; (though probably with a new name) and events like &lt;a href="https://thejam.dev/"&gt;TheJam.dev&lt;/a&gt; are still relevant for folks as they have recently tended to focus on full stack web development using JavaScript – so things like full-stack JavaScript ecosystem frameworks/tools and serverless and edge computing, none of which have become less relevant. We'll see how this continues to evolve and whether new terminology is created or even needed.&lt;/p&gt;

&lt;p&gt;The point is, the term seems to be dead but the tools and technologies it encompassed are still very much alive.&lt;/p&gt;

&lt;p&gt;Goodbye, Jamstack. It was a fun.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>jamstack</category>
    </item>
    <item>
      <title>The Art of the CFP: Getting Your Session Accepted</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Mon, 22 May 2023 13:26:09 +0000</pubDate>
      <link>https://dev.to/remotesynth/the-art-of-the-cfp-getting-your-session-accepted-20co</link>
      <guid>https://dev.to/remotesynth/the-art-of-the-cfp-getting-your-session-accepted-20co</guid>
      <description>&lt;p&gt;Speaking at developer conferences can be a great resume boost. It can help establish you as an expert in topics that interest you, help connect you with other developers who share your interest and expertise and – last but not least – help subsidize the cost of attending an event (through free tickets and/or travel stipends). But the CFP (call for papers) can be a difficult rite of passage for many folks. In this post I want to share some strategies for writing a CFP submission that will hopefully increase your chances of success.&lt;/p&gt;

&lt;p&gt;First, let me clear the air for a moment. I am not putting myself out there as having all the answers. However, I have been on both ends of the CFP as a potential speaker and as a conference organizer for about 15 years. What I hope to share are things that I've found can help a CFP stand out during the review processes I've been a part of, but also things that have worked for me in getting my own CFPs accepted. It certainly doesn't mean that I don't get rejected (I've gotten rejected by countless conferences over the course of my career), but these strategies have been effective in helping get me accepted.&lt;/p&gt;

&lt;p&gt;Second, it's worth sharing that as a conference organizer, I am not a fan of CFPs. I think they tend to heavily favor those who have a lot of experience writing CFPs and with the process of submission. This means they tend to favor folks who are often part of the "conference curcuit" (DevRel folks like me for example). This can make it tough for new speakers to break in and, sad to say, even lead to a less diverse set of speakers. I get why organizers use them – for a large conference, finding and inviting all the speakers you'd need would be a monumental task. So the CFP is often a necessary evil, but hopefully some of the tips I lay out here will help even the playing field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Topic
&lt;/h2&gt;

&lt;p&gt;There are two strategies I recommend for picking what topic or topic(s) to choose. When in doubt, always choose the first strategy because it usually makes the best talks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Talk about what you are most passionate about!&lt;/strong&gt; Whenever a speaker asks me what they should speak about, I always respond, "What topics are you most passionate about right now?" This seems like common sense, but if there is a tool/framework/strategy/topic you are excited about or that you know particularly well, pick that first. Enthusiasm for a topic can often come through in a CFP, but also these are always among the best talks and are the easiest presentations for you to create.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick a topic that seems more likely to get accepted.&lt;/strong&gt; This can often be hard to determine but I tend to look at the topic list from a conference (this is usually somewhere on the CFP page) and take an educated guess about ones that are less likely to have as much competition. In some cases, you can even reach out to the organizers and see what topics they are in most need of. Writing about the latest hot topic/tool/etc. will likely mean there are a lot of similar talk submissions to yours, but picking a more "obscure" topic might give you a better chance of standing out. I've even gone as far as to pick a topic I don't really know (yet) and used the session as a forcing function to learn it. I don't necessarily recommend this as it can be incredibly time consuming and difficult, but it was a strategy that has worked to get me into some very competetive conferences in the past.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's worth noting that if you have a topic that manages to hit both strategies, go for that!&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking a Title
&lt;/h2&gt;

&lt;p&gt;When writing the title, keep in mind the three Cs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clear&lt;/strong&gt; – Don't leave the organizer/attendee guessing what your session is really about (this is often the result of being too clever with the title). Often organizers are looking for specific topics or to make sure they have enough variety of topics. The title is the first thing they see and, especially if there are a lot of submissions, the title is often the only thing they use to determine which sessions to prioritize reviewing. So, the primary topic of your session should be immediately obvious from the title.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creative&lt;/strong&gt; – The best titles tend to have a little fun or convey some sense of urgency (or perhaps both). For a "fun" example, instead of "Building Websites with Astro" or "Getting Started with Astro", I went with "Lifting Off with Astro" which has a little play on words. For an urgency example, instead of "What is the Serverless Edge?" or "How to Use the Serverless Edge", I went with "What You Need to Know About the Serverless Edge."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concise&lt;/strong&gt; – While this isn't necessarily the case for blog posts, when it comes to session abstracts, longer titles are generally not better. I suggest trying to keep it to 5 to 10 words maximum (and yes the 10 includes words like "the", "with", "on", etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How Long should CFP Abstract Be?
&lt;/h2&gt;

&lt;p&gt;In my opinion, the ideal CFP is 1-2 &lt;em&gt;short&lt;/em&gt; paragraphs. A short paragraph is no more than 3 sentences. You don't need to cover every detail – focus on the main point and key takeaways of your presentation. If you find that you can't cover that in 2 short paragraphs, perhaps you need to refine the topic a bit. In some cases, particularly for hands-on sessions, this might be accompanied by an outline but in most cases that isn't necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structuring Your Abstract
&lt;/h2&gt;

&lt;p&gt;While it's not one-size-fits all by any means, I have found that the simple structure I lay out below tends to be very effective:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;State the "problem."&lt;/strong&gt; In a sentence or two, what is the issue that the thing you are going to teach solves. For example, if you were giving a career-oriented talk, you might say, "Navigating your career as a developer during a tech downturn can require a lot of careful planning." Or if you were giving a talk on Svelte, you might say, "As page weights have continued to grow, developers need to be more conscientious about the impact their framework has on JavaScript bundle size."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State the solution.&lt;/strong&gt; In one or two sentences, how does what you are going to teach address the problem you stated. Continuing the above examples, you might say, "However, there are a number of tried and true strategies that can help ensure that your career growth isn't stunted by a down economy." Or, "Svelte provides developers the reactivity modern web applications require on the frontend while also reducing the amount of JavaScript the client needs to download."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clarify the takeaways.&lt;/strong&gt; This is the critical piece many CFP submissions tend to forget. In a sentence or two, give some detail on what you will show and what the audience will take away from the session. Again, continuing the above examples, you might say, "In this session, I'll explore career advice from a variety of experts and show how you can practically apply these to advance your developer career." Or, "We'll dig into the code of an example Svelte application that demonstrates the core concepts you'll need to get started using the framework." Feel free to add more specifics here. For example, you might follow up that last sentence with, "By the end attendees will learn how to install and configure Svelte, how to create components and how reactivity works in Svelte."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am not saying every CFP abstract needs to follow this structure. You can feel free to be creative. In some cases it may not entirely suitable for your specific topic or the required format for a particular conference, but it is a good default. I would stress that if you do stray from this format, you should always include the takeaways regardless.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Many Sessions Should I Submit?
&lt;/h2&gt;

&lt;p&gt;Some CFPs have a limit to the number of submissions you can make, but my advice here is to submit more than one if you really want to speak at a particular event. It's important to remember that most organizers are sifting through a lot of submissions, and often can't give every submission the attention it deserves. The techniques above will hopefully help yours stand out, but submitting more than one simply increases the chances that one of your submissions is seen and the chances that you have something that fills a gap in the schedule someone else didn't already fill. In most cases, I'll submit 2-3 sessions for a given CFP (more than 3 seems to be getting excessive in my view).&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep Track of Your Submissions
&lt;/h2&gt;

&lt;p&gt;I don't mean keep track of submissions just so that you can follow up or plan, I mean save the title/abstract and other details somewhere. This is a mistake I have frequently made in the past, which is to submit a CFP without keeping a copy for myself. There are two reasons for saving these.&lt;/p&gt;

&lt;p&gt;The first is that even the most experienced speakers tend to get rejected all the time. A CFP submission takes a decent amount of work and just because it got rejected from one conference doesn't mean it will get rejected from another. Keep a copy so that you can keep refining it and submitting it. For example, create an Obsidian vault with all your submissions.&lt;/p&gt;

&lt;p&gt;The second is that talks themselves are often an enormous amount of work. If you hit on a good talk, don't be afraid to reuse it. A handful of conferences won't accept talks that have been given elsewhere, but most have no issue with it. Take any feedback you may have gotten from the previous event(s), update your abstract and reuse it. Honestly, the more I give a talk, the better it tends to get.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good Luck!
&lt;/h2&gt;

&lt;p&gt;I hope that these tips are helpful to you. And if you are looking for someone to give your CFP submission a quick review before you submit it, feel free to reach out (you can use brian at cfe dot dev). Lastly, if you are looking for a good place to give your talk, I'd love to consider your talk for &lt;a href="https://cfe.dev"&gt;cfe.dev&lt;/a&gt; where we welcome speakers of all experience levels.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Header photo by Matheus Bertelli: &lt;a href="https://www.pexels.com/photo/photo-of-people-sitting-on-chairs-3321789/"&gt;https://www.pexels.com/photo/photo-of-people-sitting-on-chairs-3321789/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
    </item>
    <item>
      <title>What Developers Need to Know About JWTs</title>
      <dc:creator>Brian Rinaldi</dc:creator>
      <pubDate>Tue, 16 May 2023 11:56:30 +0000</pubDate>
      <link>https://dev.to/cfedev/what-developers-need-to-know-about-jwts-1c9e</link>
      <guid>https://dev.to/cfedev/what-developers-need-to-know-about-jwts-1c9e</guid>
      <description>&lt;p&gt;Let's delve into JSON Web Tokens, their history, and the problems they solve. We'll discuss the importance of thorough validation, as some developers tend to overlook this aspect. Additionally, we'll cover Bearer Tokens, their issues, and possible remedies. Common mistakes made with JSON Web Tokens will also be addressed, followed by an exploration of Refresh Tokens – a related, yet distinct, concept. Lastly, we'll touch upon JSON Web Token revocation. So, let's dive in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is based upon a &lt;a href="https://cfe.dev/sessions/what-devs-need-to-know-about-jwts/"&gt;presentation from Dan Moore&lt;/a&gt; of &lt;a href="https://fusionauth.io"&gt;FusionAuth&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/RVN-hYPieMg?start=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What are JWTs?
&lt;/h2&gt;

&lt;p&gt;JSON Web Token, or JWT (pronounced "jawt" as per the specifications), is a widely recognized standard in the tech industry.&lt;/p&gt;

&lt;p&gt;JSON Web Tokens (JWT) belong to a family of specifications around RFC 7519, which include RFC 7516, 7517, 7518, 7520, and others. These standards, developed by the IETF, focus on various aspects of tokens, signing, and encryption. So, when discussing JWTs, it's essential to remember that they are part of a broader set of related standards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M2BYOHI9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9tu94k46y9xb78k64slp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M2BYOHI9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9tu94k46y9xb78k64slp.jpg" alt="JWTs briefly" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JSON Web Tokens (JWTs) first appeared around 2015-2017 and can be either signed or encrypted. A signed JWT ensures the integrity of the token, meaning you can confirm it hasn't been altered, but its contents are not secret. It's crucial not to include sensitive information like social security numbers in signed JWTs. On the other hand, encrypted JWTs keep the payload secret, as the name suggests. Understanding the difference between signed and encrypted JWTs is essential when working with these tokens.&lt;/p&gt;

&lt;p&gt;While signed JWTs are more common, it's important to note that their contents are not secret, and sensitive information should never be included in them. If a signed JWT contains a secret, you should either convert it to an encrypted JWT or find a way to exclude the secret, as anyone who obtains the JWT can view that secret. Encrypted JWTs, on the other hand, keep the payload secret, ensuring the information remains confidential.&lt;/p&gt;

&lt;p&gt;JSON Web Tokens (JWTs) are frequently used as stateless, portable tokens of identity. They are considered stateless because their authenticity and integrity can be verified without needing to communicate with a server to confirm their validity. This feature allows them to function independently, without constant server-side validation. JWTs are also portable due to their compatibility with HTTP, easily fitting into form parameters and headers.&lt;/p&gt;

&lt;p&gt;JSON Web Tokens (JWTs) can be stored in cookies and are often used as tokens of identity at the end of an authentication event, representing the user. For example, this is how FusionAuth utilizes them. The holder of the token is usually the person who authenticated, although there are some caveats to this. JWTs are particularly useful for APIs and microservices, as you can generate a JWT in one location, pass it to a client, and then have the client present it to various APIs or microservices to access data or functionality on behalf of that user. This makes JWTs ideal for distributed systems where stateless, portable identity representation is essential.&lt;/p&gt;

&lt;p&gt;JSON Web Tokens (JWTs) are widely used by various identity providers, including FusionAuth, Auth0, Firebase, and Cognito. These providers produce JWTs to grant access to specific functionalities for users holding the tokens. APIs and microservices can validate JWTs, ensuring that the access is provided to the intended users. This widespread support and adoption of JWTs across the industry reinforces their importance as a standard in stateless, portable identity representation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leveraging JSON Web Tokens in a Complex To-Do Application
&lt;/h2&gt;

&lt;p&gt;JSON Web Tokens (JWTs) have gained widespread popularity, not only because they are supported by various identity providers, but also due to extensive documentation, library support, and general understanding within the tech industry. JWTs have been around for a while, and their primary purpose is to address the critical issue of distributed identity, making them an invaluable tool in modern applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JCN_qoJ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fn1f9wk7cpkoveg4dced.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JCN_qoJ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fn1f9wk7cpkoveg4dced.jpg" alt="todo example app" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our example, we have a typical, slightly over-engineered to-do application. The architecture consists of clients on the left side, which could be browsers, native clients, or other types of clients. These clients communicate with various APIs, which in turn interact with a data store. The data store for the user API has a standard structure, containing users, roles, and a mapping between them. Meanwhile, the to-do API data store consists of a single table for to-dos. This table has some interesting features, demonstrating the complexity that can arise even in a simple to-do application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--60Jg0pLd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8b6nvmd6iupywhj6pufs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--60Jg0pLd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8b6nvmd6iupywhj6pufs.jpg" alt="user api and user databases" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The data store for the user API houses users, roles, and their mappings, while the to-do API data store contains a single table for to-dos with fields like text, status, and user ID. Notably, even in a simple to-do application, complexity can arise due to the distributed nature of the system. For instance, the user ID field cannot be a foreign key, as cross-database references are not supported. Although the user ID is always present and set to not null, it cannot reference the user table directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;TABLE&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="nx"&gt;INT&lt;/span&gt; &lt;span class="nx"&gt;NOT&lt;/span&gt; &lt;span class="nx"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="nx"&gt;TEXT&lt;/span&gt; &lt;span class="nx"&gt;NOT&lt;/span&gt; &lt;span class="nx"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="nx"&gt;INT&lt;/span&gt; &lt;span class="nx"&gt;NOT&lt;/span&gt; &lt;span class="nx"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;user_id&lt;/span&gt; &lt;span class="nc"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;NOT&lt;/span&gt; &lt;span class="nx"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PRIMARY&lt;/span&gt; &lt;span class="nc"&gt;KEY &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&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 over-engineered to-do application, accessing to-dos requires an authentication process. First, the user provides their credentials, such as a username and password, by posting the information to the user API. This authentication step is essential for ensuring secure access to the to-dos and maintaining the integrity of the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QwNSKXWY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgdecyasa5q27b3ksvkb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QwNSKXWY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgdecyasa5q27b3ksvkb.jpg" alt="app posts to the api" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing User Data and To-dos: Opaque Tokens, Validation, and Architectural Considerations
&lt;/h2&gt;

&lt;p&gt;In the over-engineered to-do application, linking the to-dos back to the user after the login event is crucial. During the login process, the user API communicates with the user database and retrieves the user's data. This user data plays a vital role in connecting the to-dos to the user, ensuring secure access and maintaining the integrity of the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XiaGlDWM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8ttve4eqth9wxbw34uj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XiaGlDWM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8ttve4eqth9wxbw34uj.jpg" alt="the api returns a user json" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the case of JSON Web Tokens for developers, the details of the authentication process are not crucial. What matters is that at the end of the process, the developer receives a user object, usually in JSON format for modern web applications. The user object typically contains information such as an identifier, name, roles, and email, among other details. This user data is passed to the client and plays a vital role in connecting the user with their to-dos, ensuring secure access, and maintaining the integrity of the system.&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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="s2"&gt;Dan Moore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dan@fusionauth.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roles&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the over-engineered to-do application, when the client wants to get the to-dos for user ID 42, they pass that identifier to the to-do API. The API then queries the to-dos database and finds that Dan has 25 different to-dos. It wraps them up as JSON and sends them back to the client, which then renders them. However, this approach is not ideal because you cannot trust the client to maintain the integrity of the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nDBW92w2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhfo3hbhr4wqx5k0naph.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nDBW92w2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhfo3hbhr4wqx5k0naph.jpg" alt="the api returns todos as json" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, a potential security issue arises from the client directly requesting to-dos using the user ID. Attackers could inspect the client code or network traffic to identify that user ID 42 is being used, giving them insight into the number of users in the system and allowing them to iterate through user IDs to access other users' to-dos. To prevent this, the to-do API must not accept user IDs blindly and needs a mechanism to protect user privacy. One potential solution is using an opaque token approach.&lt;/p&gt;

&lt;p&gt;In this scenario, the user API generates a long random string, known as an opaque token, along with the user data. The client is coded to present this token to the to-do API, rather than the user ID. While this approach addresses the issue of revealing the number of users in the system, it still leaves room for enumeration attacks. Attackers could potentially rent multiple EC2 instances on AWS and iterate through every possible token value to scrape all the to-dos. To prevent this, the to-do API cannot use the opaque token as an identifier in the to-dos database. Instead, it must present this token to the user API for validation.&lt;/p&gt;

&lt;p&gt;The user API validates the opaque token by checking its database, as it generated the token initially. If the token is valid, the user API returns the user ID or the entire user object, allowing the to-do API to retrieve the to-dos. If the token is not valid, the user API sends an error code, indicating that an issue occurred, such as a bug or an attempt at privilege escalation, and the to-do API should not return the to-dos. This approach is valid, but like any engineering issue, there are trade-offs to consider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--odiM2LP_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4otvys3ghxq2yvwgob2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--odiM2LP_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4otvys3ghxq2yvwgob2.jpg" alt="opaque tokens" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maintaining a user token mapping at the user API can range from trivial to a significant challenge, depending on the number of users. This approach can also be quite chatty, as the to-do API needs to present the token to the user API every time it receives a request, unless caching is implemented, which introduces additional complexities. Architecturally, this method heavily couples the user API to almost everything. Whenever requests come in from any user-related APIs, the token must be extracted and presented to the user API. Although this might work for systems with only a few APIs, it may become problematic as the number of APIs increases.&lt;/p&gt;

&lt;p&gt;With a growing number of APIs, scaling the user API becomes increasingly necessary. This flow is similar to OAuth introspection, an IETF-approved RFC for determining the validity of client requests. It's worth noting that this process is not exactly OAuth introspection, but shares similarities with it. Understanding these patterns and trade-offs is crucial for managing complex systems and ensuring secure access to user information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leveraging JWTs for Simplified Authentication in Complex Systems
&lt;/h2&gt;

&lt;p&gt;In the over-engineered to-do application, JWTs offer an alternative solution to the opaque token approach. The user API generates a JWT, which is then passed to the client. The client can extract the JWT and present it to the to-do API, allowing the to-do API to verify the signer, validity, recipient, and other attributes without needing to communicate with the user API. This approach simplifies the authentication process and reduces the coupling between the user API and other APIs, making it easier to manage in complex systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jdqrXSgP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bo0nc5uv27lgr5sru1b9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jdqrXSgP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bo0nc5uv27lgr5sru1b9.jpg" alt="the benefits of JWTs" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A JWT can be signed using a public-private key pair or a symmetric key, which will be discussed in more detail later. The main advantage is that the to-do API doesn't need to communicate with the user API, as the token's signature guarantees its correctness. Additionally, a JWT can hold various information, such as roles, subscription statuses, or plan details. However, it's important to note that the content of a JWT is visible to anyone who obtains it, so sensitive information should be avoided. Now, let's examine what a signed JSON Web Token looks like.&lt;/p&gt;

&lt;p&gt;In the over-engineered to-do application, a signed JSON Web Token (JWT) consists of three parts: the header (green), the payload or body (blue), and the signature (white or tan). Both the header and the payload are Base64 URL encoded strings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5p4fNNbB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b64v9euuukf7pgonc35e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5p4fNNbB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b64v9euuukf7pgonc35e.jpg" alt="the JWT payload" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's why you'll often see JWTs starting with "EYJ0", as it represents the URL-encoded string for "{" (curly brace). If you want to decode the Base64 URL encoded strings in a JWT, you can simply search for a Base64 decoder online, and it will work most of the time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--65JrCpVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1njyrkicr9k0mgu1ghtf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--65JrCpVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1njyrkicr9k0mgu1ghtf.jpg" alt="base64 encoded header" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding JWT Structure and Validation: Headers, Claims and Signatures
&lt;/h2&gt;

&lt;p&gt;In a signed JSON Web Token (JWT), the header contains metadata, such as the algorithm used to sign it, the type of JWT, and sometimes a key identifier to determine which key was used for signing. The body, where things get interesting, can store any information as it is a JSON object. The keys of the JSON object are called claims, which provide details about the user. Some claims, like issuer, expiration, audience, and subject, are standardized, while others are not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ldM3yXK5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xg6wik8ieadpv5pe80zr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ldM3yXK5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xg6wik8ieadpv5pe80zr.jpg" alt="the JWT body" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a JWT, you can store a variety of data types, such as arrays, arrays of arrays, or arrays of objects of arrays. There's no specific limit on the JWT length, but the transport layer might impose a limit. For example, if you pass a JWT as a header, some browsers might have a maximum header length, which would effectively limit the JWT size. The flexibility of JWTs allows for the inclusion of non-standardized information, like names and roles, to be helpful to downstream JWT consumers like the to-do API.&lt;/p&gt;

&lt;p&gt;In a JWT, the signature is an essential component, ensuring the integrity and authenticity of the token. Although you will most likely use a library to generate the signature, understanding its function is valuable. The process involves performing a cryptographic operation on the header and payload, followed by Base64 URL encoding the result. When passing a JWT as a cookie, you may be limited by the cookie size, requiring you to break it apart and reassemble it if necessary. Overall, the signature plays a crucial role in maintaining the security of JWTs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---7g1CDUa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5p5yzirhh5qg56wne7l.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---7g1CDUa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5p5yzirhh5qg56wne7l.jpg" alt="the JWT signature" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The type of cryptographic operation performed on a JWT depends on the chosen algorithm, which can be symmetric or asymmetric. Libraries are typically used to handle these operations, but it's essential to understand that the signature is tied to the header and the body. If any alterations are made to the header or body, such as changing a role, the signature will no longer match. This leads to the process of JSON Web Token validation, which involves the necessary steps developers need to take when receiving a JWT to ensure its integrity and authenticity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sO4yhocT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkpvtxlnwdwizn91ayuc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sO4yhocT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkpvtxlnwdwizn91ayuc.jpg" alt="signature generation" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing User Data with JWT Validation
&lt;/h2&gt;

&lt;p&gt;APIs like the to-do API receive JSON Web Tokens (JWTs) from the client to protect user data, such as personal to-dos. To ensure that only authorized users can access their to-dos, it is crucial to validate the JWT. The first step in this process is validating the signature. It is recommended to use a library for this task, as most programming languages have a dedicated JWT library available. By effectively validating the JWT, you can maintain the security and integrity of user data within the to-do application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uJkC3W4X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9svqcfprc9v8kfibgw49.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uJkC3W4X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9svqcfprc9v8kfibgw49.jpg" alt="validating the signature" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When working with JSON Web Tokens (JWTs), it's important to use a trustworthy library, preferably an open-source one, for validating the tokens. When using a library to check the JWT, if it returns an exception or error indicating that the signature doesn't match, the process should be stopped immediately. This error could result from a bug, privilege escalation, or other issues, but it signifies that the JWT is not valid for further processing.&lt;/p&gt;

&lt;p&gt;After validating the signature, it's essential to validate the claims within the JWT. This is a step that some developers may overlook, but it is crucial for maintaining the security and integrity of user data within applications like the to-do API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bN72pu11--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2tupy6ecwlu3fon0345.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bN72pu11--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2tupy6ecwlu3fon0345.jpg" alt="validating the claims" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After checking the JWT signature, you must validate the values of the JSON object keys, which can be considered business logic. While some libraries can help with certain claims, you'll need to handle custom claims like checking for a paid user. This means you need to write the logic for verifying these custom attributes. Although you have to write this logic, you can create a custom library or module to avoid writing it for each individual API. This will ensure the security and integrity of user data within applications like the to-do API.&lt;/p&gt;

&lt;p&gt;In a JWT, both standardized and custom claims exist. For example, the issuer claim verifies that the JWT was created by the expected entity, like the user API in a to-do application. It's essential to check the issuer to ensure the JWT is generated by the correct party. Another claim to validate is the expiration time, which is the number of seconds since the Unix epoch. If the expiration time is in the future, the JWT is most likely valid. Validating these claims helps maintain the security and integrity of the user data within applications.&lt;/p&gt;

&lt;p&gt;In a JWT, it's crucial to validate claims like expiration time and audience. If the expiration time is in the past, the JWT is invalid, and processing should stop, just as if the signature validation failed. The audience claim indicates the intended recipient of the JWT. In the given scenario, the to-do API is the intended recipient. If the audience claim doesn't match the expected value, processing should be halted. This is important because there could be multiple APIs with different user roles, and validating the audience ensures that the JWT is being used for the correct API.&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="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fusionauth.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;exp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1619555018&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aud&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;238d4793-70de-4183-9707-48ed8ecd19d9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sub&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;19016b73-3ffa-4b26-80d8-aa9287738677&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;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="s2"&gt;Dan Moore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;roles&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RETRIEVE_TODOS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ADMIN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When dealing with JWTs in applications, it is essential to ensure the proper access level is granted, depending on the user's role in various APIs. For example, an admin in the todo API might have access to view other users' to-dos, while an admin in the accounting API may have the ability to send out checks. It is crucial that a JWT created for the to-do API is not presented to the accounting API, as it could lead to unauthorized access. To handle this, you can implement validation code which outlines a simple validation scenario to maintain the security and integrity of user data across different APIs.&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;// the todo api&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;algorithms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HS256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ignoreExpiration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;issuer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fusionauth.io&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;verified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hmac_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// addl verification checks&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;verified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aud&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myapp.example.com&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;throw&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid audience&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;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;verified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;premiumUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid access&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the given example, if the HMAC key is changed, it demonstrates what happens when the signatures of JWTs don't match. By creating a new HMAC key (&lt;code&gt;const new HMAC key&lt;/code&gt;), the signature will be different, as the time has changed. This mismatch in signatures helps illustrate the importance of validating JWTs to ensure the security and integrity of user data within applications like the to-do API. When signatures don't match, it's crucial to halt further processing and investigate potential issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OG39SVlF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2qev5kyaklytwk8qyu2e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OG39SVlF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2qev5kyaklytwk8qyu2e.jpg" alt="failed validation" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing User Data: The Importance of Bearer Token Security
&lt;/h2&gt;

&lt;p&gt;We've examined the architectural benefits of using JSON Web Tokens (JWTs) and learned about their structure. We've also discussed the importance of validating JWTs to ensure the security and integrity of user data in applications like the to-do API. Now, let's shift our focus to bearer tokens in general, which are closely related to JWTs. Bearer tokens are authentication tokens that grant access to resources on behalf of the bearer or holder, making them a convenient method for authorizing users in various scenarios.&lt;/p&gt;

&lt;p&gt;Bearer tokens are a broader concept than JSON Web Tokens (JWTs), as JWTs are often used as bearer tokens, but bearer tokens can exist independently of JWTs. The main idea behind a bearer token is that possession of the token serves as proof of access. A helpful analogy to understand this concept is a car key – possessing the key grants access to the car.&lt;/p&gt;

&lt;p&gt;If you have a simple car key without a biometric sensor, anyone holding the key can access and drive the car. Therefore, you must be cautious about the key's location, as leaving it unattended could allow unauthorized individuals to take the car. Similarly, in the world of JWTs, you need to safeguard the JWT issued by the user API. If a malicious actor acquires the JWT, they can present it to the JWT API and potentially gain unauthorized access.&lt;/p&gt;

&lt;p&gt;The JWT API will consider a stolen JWT as valid because the malicious actor didn't create a new JWT but instead used an existing one. This can lead to unauthorized access to user data, making it essential to be cautious about storing and transmitting tokens. Protecting tokens during transit is crucial, and HTTPS serves as the gold standard for ensuring secure communication.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yd6AWEoV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jrczmvwbxvxbbv7jz31x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yd6AWEoV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jrczmvwbxvxbbv7jz31x.jpg" alt="token storage" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Securing JWTs: Common Issues
&lt;/h2&gt;

&lt;p&gt;Avoid placing sensitive JWTs in URLs, as they may be cached as query parameters. For token storage, server-side storage is generally recommended, although use cases may vary. If a browser is used as a client, store JWTs as &lt;code&gt;HtttpOnly&lt;/code&gt; secure cookies to eliminate cross-site scripting issues. For native clients, use secure storage options like keychains or Android's secure storage.&lt;/p&gt;

&lt;p&gt;You should also be aware that a JSON Web Token (JWT) can leak information even if you don't store secrets in it. For example, if the audience claim is a number instead of a UUID, it may reveal the number of users in the system. Since JWTs are not secret, using a number like 42 for the audience ID or subject claim could inadvertently disclose that there are at least 42 users. To avoid this, use UUIDs for such claims. Additionally, when using symmetric key signing, like HMAC, the secret is shared between the entity signing the JWT and the entity verifying it, so take extra care to ensure the integrity and confidentiality of shared secrets.&lt;/p&gt;

&lt;p&gt;It is possible to brute force a JSON Web Token (JWT) secret simply by using the token itself. This involves trying different secrets and running the cryptographic algorithm on the header and body, checking if the signature matches. This process continues with various secrets until a match is found. A GitHub project demonstrates this technique in C. The time taken to brute force the secret depends on the HMAC key length; shorter keys are quicker to crack, while longer keys take more time. This highlights the importance of avoiding the "none" algorithm for signing headers, as it leaves JWTs without any signature, making them vulnerable to exploitation.&lt;/p&gt;

&lt;p&gt;Essentially, when there is no integrity check, anyone who discovers an API that accepts a JWT with no algorithm or signature can create any payload they want, base64 encode it, and present it to the API. This results in a completely valid JWT, emphasizing the importance of having proper checks in place to maintain the security and integrity of user data within applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancing Security and Integrity of JWTs with Asymmetric Key Pairs
&lt;/h2&gt;

&lt;p&gt;When dealing with JWTs, it's crucial to avoid using the "none" algorithm, as it's like accepting unsanitized user input – but worse. This is because unsanitized credentials might lead to unauthorized access, compromising the security and integrity of user data within applications. To further enhance security, it's important to understand the differences between asymmetric and symmetric key pairs. Asymmetric key pairs involve a public and private key, while symmetric key pairs use a shared secret for both signing and verifying JWTs. Knowing the appropriate key pair type for your scenario can help maintain the confidentiality and integrity of your JWTs.&lt;/p&gt;

&lt;p&gt;In our example, the user API generates a JSON Web Token (JWT) and passes it to the client, which then presents it to the to-do API. As previously mentioned, HMAC is a common algorithm used for this purpose, which means that both the user API and the to-do API share a secret. This shared secret is essential for maintaining the security and integrity of user data across different APIs.&lt;/p&gt;

&lt;p&gt;Bearer tokens, such as JSON Web Tokens (JWTs), need to be protected and securely stored. When using symmetric key signing, like HMAC, sharing and distributing the secret securely is crucial. One option is to distribute multiple secrets during deployment for rotation purposes. However, asymmetric key pairs can help avoid this complexity. Asymmetric key pairs, like RSA or elliptic curve cryptography, use a private key to sign the JWT and a public key to verify it. This approach simplifies the distribution and management of keys, enhancing the security and integrity of user data across different APIs.&lt;/p&gt;

&lt;p&gt;Using asymmetric key pairs can significantly improve scalability in various situations. For instance, sharing a secret between a small development team maintaining two or three APIs might not be a big issue. However, if you have two different companies or departments, sharing a secret becomes a much more significant concern. In our example, let's say Pied Piper owns the user API, and Hooli owns the JWT API. Using asymmetric key pairs in such a scenario simplifies the distribution and management of keys, ultimately enhancing the security and integrity of user data across different APIs.&lt;/p&gt;

&lt;p&gt;The user API generates a JWT, signs it with a private key, and passes it to the client. The client then presents the JWT to the JWT API, which can obtain the public key (since it's public) and verify the JWT. This approach allows for scaling out to include other organizations. Security is also a crucial factor. When using a symmetric key pair, the same secret is used to sign and verify the JWT. This increases the attack surface, as every entity verifying a JWT needs to be as secure as the user API. Using asymmetric key pairs, like RSA or elliptic curve cryptography, can help mitigate this issue and maintain the security and integrity of user data across different APIs.&lt;/p&gt;

&lt;p&gt;When using symmetric key signing like HMAC for JWTs, the security implications are significant. If someone steals the shared secret, they can create JWTs with any desired access, such as super admin privileges, until the secret is rotated. This poses a substantial risk to user data and API security. However, asymmetric key pairs for JWTs are between three and 10 times slower than symmetric key signing. If you're processing a large number of JWTs, the trade-off between security and performance might be worth considering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Importance of Refresh Tokens
&lt;/h2&gt;

&lt;p&gt;Refresh tokens are essential because access tokens, especially for users, are meant to be short-lived, lasting only seconds or minutes. In contrast, refresh tokens can be long-lived. They are presented to the user API, which then generates new JSON Web Tokens (JWTs) as needed. This helps maintain the security and integrity of user data within applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--odHrE8b3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4vgg9gxdepw433rsfva.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--odHrE8b3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4vgg9gxdepw433rsfva.jpg" alt="refresh token lifetimes" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refresh tokens play a crucial role in maintaining a balance between security and user experience. Without them, you'd have two undesirable alternatives. One is having short-lived access tokens, like five minutes, which would force users to log in frequently, leading to a poor user experience. The other option is having long-lived tokens, such as a month, which compromises security. Refresh tokens help avoid these issues by allowing users to obtain new JWTs without constantly logging in, ensuring a more seamless and secure experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Balancing Security and User Experience with Refresh Tokens in JWT-Based Systems
&lt;/h2&gt;

&lt;p&gt;If someone manages to steal a JSON Web Token (JWT), they would have an extended period to exploit the token, pulling down data and potentially causing harm. This is why it's essential to strike a balance between security and user experience using refresh tokens, which allow users to obtain new JWTs without logging in frequently. This approach ensures a more seamless and secure experience, minimizing the risk of unauthorized access and protecting user data.&lt;/p&gt;

&lt;p&gt;Refresh tokens operate by being requested during the authentication process and being sent along with the JWT. The JWT is then presented to the API, and data is exchanged. This pattern continues throughout the JWT's lifetime.&lt;/p&gt;

&lt;p&gt;Eventually, the JSON Web Token (JWT) will expire, and the two APIs will deny further access to the user's to-dos. At this point, the client can present the refresh token to the user API. The user API checks if the user is still valid, logged in, and has paid their bill, then issues a new JWT. The new JWT is sent to the client, which can then present it to the to-do API for validation. This process allows for re-authentication of the user in a silent manner, ensuring a seamless and secure experience.&lt;/p&gt;

&lt;p&gt;In a JSON Web Token (JWT)-based system, revoking access during logout is primarily done by revoking the refresh token. When a user chooses to log out, the client sends a message to the user API to stop issuing JWTs for the associated refresh token and then deletes them from its storage. However, revoking the JWT itself is more complex, and there isn't a straightforward method. RFC 7009 provides a way to revoke refresh tokens, but using this specification could result in losing certain capabilities.&lt;/p&gt;

&lt;p&gt;Removing a public key from the list of public keys is one way to revoke access, as the library won't be able to find the valid public key, resulting in a failed signature check. Another option is using very short lifetimes for JWTs, making them almost one-time use. A third option is employing a deny list, which is a FusionAuth-specific feature, but similar implementations exist in other libraries. When a user logs out, an event is triggered and sent to different APIs, notifying them that a specific refresh token has been revoked. This helps maintain security and integrity across various APIs.&lt;/p&gt;

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

&lt;p&gt;JWTs serve as a valuable tool in the architectural tool belt, offering both advantages and disadvantages. While they can be used alongside web sessions, web sessions come with their own set of costs and benefits, such as using cookies and being less scalable. Despite these limitations, web sessions are simpler to understand. JWTs are not a one-size-fits-all solution, but they provide a powerful option for maintaining security and user experience across various APIs.&lt;/p&gt;

&lt;p&gt;Additional resources, such as the JWT debugger and sample code, can help deepen your understanding of JSON Web Tokens. In-depth articles are available for further reading, providing comprehensive insights into JWTs. The FusionAuth Community Edition, a free authentication and authorization server, can also be a valuable tool in your technical arsenal. If you have any questions or require clarification, don't hesitate to ask.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional Info&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fusionauth.io/learn/expert-advice/dev-tools/jwt-decoder"&gt;JWT Decoder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/FusionAuth/fusionauth-example-javascript-jwt"&gt;JavaScript Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fusionauth.io/learn/expert-advice/tokens/building-a-secure-jwt"&gt;Building a Secure JWT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fusionauth.io/learn/expert-advice/tokens/jwt-components-explained"&gt;JWT Components Explained&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>security</category>
    </item>
  </channel>
</rss>
