<?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: Yevhen Kozachenko 🇺🇦🇩🇪</title>
    <description>The latest articles on DEV Community by Yevhen Kozachenko 🇺🇦🇩🇪 (@ekwoster).</description>
    <link>https://dev.to/ekwoster</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3415300%2F145683af-9716-4263-812e-25a3962d2552.jpg</url>
      <title>DEV Community: Yevhen Kozachenko 🇺🇦🇩🇪</title>
      <link>https://dev.to/ekwoster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ekwoster"/>
    <language>en</language>
    <item>
      <title>A Beginner's Guide to Form Backends: What They Are and When You Need One</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Mon, 31 Aug 2026 15:50:00 +0000</pubDate>
      <link>https://dev.to/ekwoster/a-beginners-guide-to-form-backends-what-they-are-and-when-you-need-one-25ne</link>
      <guid>https://dev.to/ekwoster/a-beginners-guide-to-form-backends-what-they-are-and-when-you-need-one-25ne</guid>
      <description>&lt;h1&gt;
  
  
  A Beginner's Guide to Form Backends: What They Are and When You Need One — with onsubmit.dev (form backend)
&lt;/h1&gt;

&lt;p&gt;You built a portfolio site. The contact form looks great. The validation works. Then you click “Send” and realize there’s one missing piece: where does the form data actually go? This is where a form backend comes in. Services such as &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev (form backend)&lt;/a&gt; give your form an endpoint that can receive submissions without requiring you to build and deploy your own server.&lt;/p&gt;

&lt;p&gt;If you’re learning frontend development, this distinction can be confusing at first. Let’s look at what happens after a user presses Submit, what “form backend as a service” means, and when using one makes more sense than writing an API yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  A form is only the frontend
&lt;/h2&gt;

&lt;p&gt;Consider a contact form containing a few familiar fields:&lt;/p&gt;

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

&lt;p&gt;HTML can collect those values, and JavaScript can validate them or improve the user experience. But something still needs to receive the submitted data and do something useful with it.&lt;/p&gt;

&lt;p&gt;In a traditional application, the flow might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   |
   | POST form data
   v
Your API server
   |
   +--&amp;gt; validate input
   +--&amp;gt; store data
   +--&amp;gt; send email/notification
   +--&amp;gt; return a response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That API server is the form's backend.&lt;/p&gt;

&lt;p&gt;Building one can mean creating an API route, configuring a server or serverless function, validating input, handling errors, managing secrets, preventing abuse, and deploying and maintaining the whole thing.&lt;/p&gt;

&lt;p&gt;Those are valuable skills to learn. They can also be unnecessary infrastructure when all you want is a working contact form.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a “form backend as a service”?
&lt;/h2&gt;

&lt;p&gt;A form backend service provides the server-side endpoint for you.&lt;/p&gt;

&lt;p&gt;Instead of sending submissions to an API that you wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser -&amp;gt; Your frontend -&amp;gt; Your API -&amp;gt; Destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you send them to a managed form backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser -&amp;gt; Your frontend -&amp;gt; Form backend service -&amp;gt; Destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important idea is that your website can remain frontend-only.&lt;/p&gt;

&lt;p&gt;For example, you could host a static portfolio on a platform or CDN without provisioning your own application server just to process its contact form.&lt;/p&gt;

&lt;p&gt;A form backend generally takes responsibility for receiving the HTTP request and processing the submission. Depending on the service and configuration, it may also cover things such as validation, notifications, storage, or integrations.&lt;/p&gt;

&lt;p&gt;That makes this pattern particularly useful for static sites and relatively simple forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use one?
&lt;/h2&gt;

&lt;p&gt;A managed form backend is a good fit when the form itself isn't an important part of your application's business logic.&lt;/p&gt;

&lt;p&gt;Common examples include portfolio contact forms, landing-page inquiries, feedback forms, waitlists, and simple lead-generation forms.&lt;/p&gt;

&lt;p&gt;Imagine you're a junior developer building your portfolio. Creating an entire Express application, database, deployment pipeline, and email integration just so recruiters can send you a message probably isn't the best use of the project's complexity budget.&lt;/p&gt;

&lt;p&gt;A form backend can keep that portfolio essentially static while still giving the form somewhere useful to send its data.&lt;/p&gt;

&lt;p&gt;It can also be handy for prototypes. If you're testing whether people are interested in an idea, getting the form online quickly may matter much more than designing a permanent backend architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you build the backend yourself?
&lt;/h2&gt;

&lt;p&gt;Form backends aren't substitutes for every API.&lt;/p&gt;

&lt;p&gt;Suppose you're creating a banking application and a submitted form initiates a money transfer. That isn't simply a message you need to collect. It involves authentication, authorization, account state, transactional database operations, auditing, and application-specific security rules.&lt;/p&gt;

&lt;p&gt;That belongs in application backend code you control.&lt;/p&gt;

&lt;p&gt;The same applies when a submission is deeply tied to your domain model. Creating an order, modifying a user account, accessing sensitive internal systems, or running a complicated application-specific workflow will usually require your own backend.&lt;/p&gt;

&lt;p&gt;A useful question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Am I collecting a submission, or executing my application's business logic?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For straightforward collection, a managed service may be sufficient. For application business logic, build the appropriate backend.&lt;/p&gt;

&lt;p&gt;There is also plenty of room between those extremes. Your requirements can grow over time, so choosing a managed service today doesn't prevent you from replacing it with your own API later.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example with onsubmit.dev (form backend)
&lt;/h2&gt;

&lt;p&gt;As an example of the managed approach, onsubmit.dev (form backend) provides form endpoints without requiring you to operate your own backend.&lt;/p&gt;

&lt;p&gt;The service also provides integrations/packages for React, Next.js, Vue, and Astro, so it can fit into projects built with those frameworks rather than requiring you to restructure the application around a separate server.&lt;/p&gt;

&lt;p&gt;The important architectural change is small:&lt;br&gt;
&lt;/p&gt;

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

&amp;lt;form&amp;gt;
    ↓
Nowhere to send the data


With a form backend:

&amp;lt;form&amp;gt;
    ↓
Managed endpoint
    ↓
Submission gets processed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build the actual implementation, create/configure the form endpoint and follow the service's documentation for your framework at &lt;code&gt;https://onsubmit.dev/docs&lt;/code&gt; and &lt;code&gt;https://onsubmit.dev/integrations&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is worth emphasizing for beginners: an endpoint URL isn't just a magic string. It represents the server receiving your HTTP request. With a managed form backend, someone else operates that server-side layer for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about JavaScript validation?
&lt;/h2&gt;

&lt;p&gt;Client-side validation and backend processing solve different problems.&lt;/p&gt;

&lt;p&gt;For example, your frontend might check whether an email looks valid before allowing a submission. That's useful because users receive immediate feedback.&lt;/p&gt;

&lt;p&gt;But frontend validation runs in the user's browser and can be bypassed. You should never treat it as a security boundary.&lt;/p&gt;

&lt;p&gt;Think of the responsibilities this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend:
- Display the form
- Improve UX
- Catch obvious input mistakes
- Show loading/success/error states

Backend:
- Receive the request
- Treat input as untrusted
- Process the submission
- Perform server-side checks
- Return a result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That remains true whether you wrote the backend yourself or use a form backend service.&lt;/p&gt;

&lt;h2&gt;
  
  
  “But shouldn't I build it myself to learn?”
&lt;/h2&gt;

&lt;p&gt;Sometimes, absolutely.&lt;/p&gt;

&lt;p&gt;If your goal is to learn backend development, building a form API is a great exercise. You can learn HTTP methods, request bodies, server-side validation, status codes, databases, environment variables, email APIs, security, and deployment from a relatively approachable project.&lt;/p&gt;

&lt;p&gt;But learning how to build infrastructure and deciding whether a production project needs that infrastructure are two different skills.&lt;/p&gt;

&lt;p&gt;Developers routinely rely on managed databases, authentication providers, payment processors, email APIs, cloud storage, and other services. Form handling can be another example of choosing a managed component when implementing it yourself provides little project-specific value.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main takeaway
&lt;/h2&gt;

&lt;p&gt;Submitting a form is fundamentally an HTTP interaction: the browser collects some data and sends it somewhere. The moment you need to receive, validate, store, forward, or otherwise process that data on a server, you've entered backend territory.&lt;/p&gt;

&lt;p&gt;You then have a choice.&lt;/p&gt;

&lt;p&gt;Build that backend yourself when the submission involves your application's business logic, specialized security requirements, or workflows that require deeper control. Consider a form backend as a service when you're primarily collecting straightforward submissions and don't want to operate server-side infrastructure just for that purpose.&lt;/p&gt;

&lt;p&gt;Understanding that tradeoff is more useful than always reaching for one solution. The goal isn't to avoid backend development—it's to know when your project actually needs a custom backend and when a managed endpoint is enough.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>staticwebapps</category>
    </item>
    <item>
      <title>Airtable as a Lightweight CRM: Capturing Website Leads Automatically</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Sun, 30 Aug 2026 15:47:00 +0000</pubDate>
      <link>https://dev.to/ekwoster/airtable-as-a-lightweight-crm-capturing-website-leads-automatically-l88</link>
      <guid>https://dev.to/ekwoster/airtable-as-a-lightweight-crm-capturing-website-leads-automatically-l88</guid>
      <description>&lt;h1&gt;
  
  
  Airtable as a Lightweight CRM: Capturing Website Leads Automatically with onsubmit.dev (form backend)
&lt;/h1&gt;

&lt;p&gt;For a small business or solo founder, a full CRM can be more infrastructure than you need. If your workflow is essentially “website visitor submits a form → lead appears somewhere I can track it,” Airtable plus &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev (form backend)&lt;/a&gt; can provide a lightweight alternative without requiring you to build and maintain your own form-processing backend.&lt;/p&gt;

&lt;p&gt;This setup is particularly useful for early-stage projects where you want structured lead management now but aren't ready to adopt HubSpot, Salesforce, or another dedicated CRM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use Airtable as a CRM?
&lt;/h2&gt;

&lt;p&gt;Airtable isn't a CRM in the traditional sense. It's a flexible database/spreadsheet hybrid, which makes it surprisingly effective for a simple sales pipeline.&lt;/p&gt;

&lt;p&gt;For example, a &lt;code&gt;Leads&lt;/code&gt; table might contain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;Contact name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;Contact details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Company&lt;/td&gt;
&lt;td&gt;Company or project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message&lt;/td&gt;
&lt;td&gt;Original inquiry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;New, Contacted, Qualified, Won, Lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;Website, referral, campaign, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Created&lt;/td&gt;
&lt;td&gt;Submission date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notes&lt;/td&gt;
&lt;td&gt;Follow-up notes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can then create Airtable views for new leads, qualified opportunities, customers, or leads that need follow-up.&lt;/p&gt;

&lt;p&gt;For a founder handling a modest number of inquiries, that can be enough CRM.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing piece: getting website forms into Airtable
&lt;/h2&gt;

&lt;p&gt;The awkward part is usually connecting your public website to your lead database.&lt;/p&gt;

&lt;p&gt;You could have the browser call Airtable's API directly, but that would require credentials that shouldn't be exposed in client-side code. The safer conventional solution is your own API endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Website form
      ↓
Your API/serverless function
      ↓
Airtable API
      ↓
Leads table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That API isn't necessarily difficult to write, but it introduces another component to deploy, secure, monitor, and maintain.&lt;/p&gt;

&lt;p&gt;A form backend removes that component from your application.&lt;/p&gt;

&lt;p&gt;With onsubmit.dev (form backend), the architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Website form
      ↓
Form backend
      ↓
Airtable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your site's job is simply to collect the lead.&lt;/p&gt;

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

&lt;p&gt;Start by creating an Airtable base for your leads. Keep the initial schema small; &lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;Email&lt;/code&gt;, &lt;code&gt;Company&lt;/code&gt;, &lt;code&gt;Message&lt;/code&gt;, &lt;code&gt;Status&lt;/code&gt;, and &lt;code&gt;Created&lt;/code&gt; are usually plenty.&lt;/p&gt;

&lt;p&gt;Then configure your form submission workflow and Airtable integration using the service's documentation. It's worth checking the current integration instructions rather than hard-coding an example from a blog post, especially because authentication and integration configuration can change over time.&lt;/p&gt;

&lt;p&gt;The goal is to map incoming form fields to the corresponding Airtable columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name    → Name
email   → Email
company → Company
message → Message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful pattern is to give every newly created record a default &lt;code&gt;New&lt;/code&gt; status. Your Airtable workflow can then be built around processing records with that status.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about React, Next.js, Vue, and Astro?
&lt;/h2&gt;

&lt;p&gt;This approach isn't tied to a particular frontend stack.&lt;/p&gt;

&lt;p&gt;onsubmit.dev (form backend) provides integrations/packages for React, Next.js, Vue, and Astro, so the same basic architecture can work whether you have a static marketing site or an application built with one of those frameworks.&lt;/p&gt;

&lt;p&gt;For implementation code, use the current examples in the product documentation rather than copying an approximation: that ensures package names, APIs, and configuration match the version you're actually installing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning Airtable into a small sales pipeline
&lt;/h2&gt;

&lt;p&gt;Once submissions arrive automatically, Airtable becomes much more useful than an inbox full of contact-form notifications.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;Status&lt;/code&gt; single-select field with values such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;New → Contacted → Qualified → Won / Lost&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can then use filtered views to answer practical questions: Which leads haven't received a response? Which opportunities are qualified? Where did this month's inquiries originate?&lt;/p&gt;

&lt;p&gt;Add fields such as estimated value, next follow-up date, owner, and source only when you actually need them.&lt;/p&gt;

&lt;p&gt;This incremental approach is one advantage of using Airtable at an early stage. You don't have to design your entire sales process before receiving your first lead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not start with HubSpot or Salesforce?
&lt;/h2&gt;

&lt;p&gt;You certainly can. Dedicated CRMs provide capabilities Airtable doesn't, including sophisticated sales automation, contact timelines, reporting, permissions, email tooling, and integrations across larger sales organizations.&lt;/p&gt;

&lt;p&gt;The tradeoff is complexity.&lt;/p&gt;

&lt;p&gt;For an early-stage project with one person answering a handful of inquiries, a CRM implementation can be premature. Airtable's free tier combined with a form backend can cover the narrower requirement: capture leads reliably, put them into a structured pipeline, and make sure somebody follows up.&lt;/p&gt;

&lt;p&gt;There are limits. Airtable's free tier has usage restrictions, while your form service may have its own limits or pricing. As lead volume and sales processes grow, a purpose-built CRM can become the better choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical migration path
&lt;/h2&gt;

&lt;p&gt;Starting lightweight doesn't mean committing to Airtable forever.&lt;/p&gt;

&lt;p&gt;Your website form can remain conceptually separate from the system where leads ultimately live. If the business eventually moves to a dedicated CRM, you can migrate records and change the destination rather than redesigning the entire website.&lt;/p&gt;

&lt;p&gt;That's a useful rule for early-stage infrastructure: optimize for today's requirements while avoiding unnecessary coupling.&lt;/p&gt;

&lt;p&gt;For a solo founder or small team, Airtable plus a hosted form backend occupies a useful middle ground between manually copying inquiries from email and building a complete CRM integration yourself. It gets leads out of your inbox and into a trackable pipeline while keeping the website architecture small.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>staticwebapps</category>
    </item>
    <item>
      <title>Webhook-First Form Handling: Piping Submissions Into Your Own Stack</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Sat, 29 Aug 2026 15:45:00 +0000</pubDate>
      <link>https://dev.to/ekwoster/webhook-first-form-handling-piping-submissions-into-your-own-stack-31gi</link>
      <guid>https://dev.to/ekwoster/webhook-first-form-handling-piping-submissions-into-your-own-stack-31gi</guid>
      <description>&lt;h1&gt;
  
  
  Webhook-First Form Handling: Piping Submissions Into Your Own Stack with onsubmit.dev (form backend)
&lt;/h1&gt;

&lt;p&gt;For backend-heavy applications, form handling rarely ends with “send me an email.” A submission may need to enter a queue, be normalized and persisted, create a CRM lead, or trigger an internal workflow. &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev (form backend)&lt;/a&gt; can sit at the public edge of that architecture: the browser submits to a managed form endpoint, while your own stack receives the resulting webhook and performs the domain-specific work.&lt;/p&gt;

&lt;p&gt;The interesting part of this approach is the separation between public form ingestion and application processing. Your backend no longer needs a dedicated internet-facing form controller just to deal with browser submissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;A webhook-first pipeline can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser / static site
        |
        | form POST
        v
onsubmit.dev (form backend)
        |
        | webhook
        v
Your webhook endpoint
        |
        +----&amp;gt; Database
        |
        +----&amp;gt; Queue / event bus
        |
        +----&amp;gt; CRM
        |
        +----&amp;gt; Internal services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful when the frontend and backend have different deployment lifecycles. A marketing site can remain static, for example, while submissions ultimately enter infrastructure owned by the backend team.&lt;/p&gt;

&lt;p&gt;The ingestion layer deals with accepting the form submission. Your webhook handler remains responsible for business semantics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why put an ingestion layer in front?
&lt;/h2&gt;

&lt;p&gt;You could expose &lt;code&gt;/api/contact&lt;/code&gt; from your application and POST directly to it. For many products, that's entirely reasonable.&lt;/p&gt;

&lt;p&gt;The webhook-first model becomes more interesting when forms are outside your main application boundary: documentation sites, landing pages, Astro-generated sites, campaign pages, or independently deployed React/Vue frontends.&lt;/p&gt;

&lt;p&gt;Instead of making every frontend deployment aware of your internal application topology, you get a boundary like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;untrusted/public input
       ↓
form ingestion
       ↓
webhook boundary
       ↓
trusted application pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That boundary lets your application concentrate on what happens after ingestion.&lt;/p&gt;

&lt;p&gt;For example, a “request a demo” submission might result in a durable internal event rather than a synchronous chain of API calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;form.accepted
    ↓
queue
    ├──&amp;gt; lead persistence worker
    ├──&amp;gt; CRM synchronization worker
    └──&amp;gt; analytics worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a temporary CRM outage does not necessarily have to make the original form-processing workflow fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep webhook handlers boring
&lt;/h2&gt;

&lt;p&gt;A common webhook mistake is doing every piece of work before returning the HTTP response.&lt;/p&gt;

&lt;p&gt;Imagine this handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receive webhook
    ↓
validate
    ↓
insert database row
    ↓
call CRM
    ↓
send notification
    ↓
update analytics
    ↓
return 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have just coupled webhook acknowledgement to the latency and availability of several unrelated systems.&lt;/p&gt;

&lt;p&gt;For production systems, a better boundary is usually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receive
  → authenticate/validate
  → assign or read an event ID
  → durably persist/enqueue
  → acknowledge

worker
  → normalize
  → apply business rules
  → update DB
  → synchronize CRM
  → trigger downstream events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the webhook endpoint a thin adapter rather than another business-logic controller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assume delivery can happen more than once
&lt;/h2&gt;

&lt;p&gt;Once a form submission becomes a webhook, treat it like any other distributed event.&lt;/p&gt;

&lt;p&gt;In particular, consumers should be idempotent. If the same submission reaches your endpoint twice, processing it twice should not create two CRM contacts, send two welcome sequences, or produce duplicate database records.&lt;/p&gt;

&lt;p&gt;If the incoming event has a stable unique identifier, store it alongside the processing state and enforce uniqueness at the database level. Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;webhook_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'received'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A uniqueness constraint is substantially safer than an application-level “check whether this exists, then insert” sequence, which can race under concurrent delivery.&lt;/p&gt;

&lt;p&gt;Downstream integrations need the same consideration. If your CRM API supports idempotency keys, propagate a stable identifier into those calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve the raw event
&lt;/h2&gt;

&lt;p&gt;It is tempting to immediately transform a form payload into your internal &lt;code&gt;Lead&lt;/code&gt; or &lt;code&gt;ContactRequest&lt;/code&gt; model and discard the original.&lt;/p&gt;

&lt;p&gt;Keeping the raw webhook payload is often worth the small storage cost.&lt;/p&gt;

&lt;p&gt;It gives you something to inspect when a mapping changes, a worker has a bug, or an external integration needs to be replayed. A useful internal event record typically contains the provider/event identifier, receipt timestamp, raw payload, processing status, attempt count, and last failure.&lt;/p&gt;

&lt;p&gt;That creates a useful distinction between “we received this submission” and “all downstream effects completed successfully.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat the webhook as an external API
&lt;/h2&gt;

&lt;p&gt;Putting onsubmit.dev (form backend) in front of the form does not make the receiving webhook inherently trusted. The webhook route is still an externally reachable integration boundary.&lt;/p&gt;

&lt;p&gt;Use the authentication or verification mechanism documented for the integration, validate what you receive, enforce payload limits where appropriate, and keep credentials out of frontend code. Consult the current service documentation before implementing verification rather than assuming a particular signature scheme or payload format.&lt;/p&gt;

&lt;p&gt;After authenticity checks, apply your own domain validation as well. Transport validity and business validity are different questions.&lt;/p&gt;

&lt;p&gt;A syntactically valid submission can still contain a product ID your application does not recognize or a field value that violates an internal invariant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version your internal contract
&lt;/h2&gt;

&lt;p&gt;External form fields tend to evolve independently from backend models.&lt;/p&gt;

&lt;p&gt;Instead of letting a form payload flow unchanged through every service, normalize it at the ingestion boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;external submission
       ↓
webhook adapter
       ↓
internal FormSubmissionReceived v1
       ↓
business consumers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That adapter is the right place to rename fields, convert strings to internal types, attach application metadata, and translate a particular form into your domain vocabulary.&lt;/p&gt;

&lt;p&gt;It also prevents a marketing-site field rename from silently becoming a breaking schema change across several backend consumers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure handling matters more than the happy path
&lt;/h2&gt;

&lt;p&gt;For a serious pipeline, decide what each type of failure means before shipping it.&lt;/p&gt;

&lt;p&gt;Transient downstream failures should normally be retryable. Invalid domain data may belong in a dead-letter path. Permanent CRM rejection should be observable without forcing endless retries. Database and queue operations should have clear durability semantics.&lt;/p&gt;

&lt;p&gt;Monitoring should answer at least three questions: Are submissions arriving? Are they being processed? Are downstream side effects succeeding?&lt;/p&gt;

&lt;p&gt;Those are separate health signals. A webhook endpoint returning successful responses says little about whether a queue worker has been failing for six hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this pattern fits
&lt;/h2&gt;

&lt;p&gt;Webhook-first processing is a particularly clean fit for teams that already operate queues, workers, event-driven services, or centralized integration infrastructure. It also helps when multiple frontend stacks need the same ingestion mechanism; the service provides integrations/packages for Astro, Next.js, React, and Vue.&lt;/p&gt;

&lt;p&gt;For a tiny site where the only requirement is persisting a row in the same application database, adding another architectural boundary may not buy much. Directly handling the POST yourself can remain the simpler design.&lt;/p&gt;

&lt;p&gt;The value appears when public form ingestion and internal business processing are genuinely different concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful abstraction
&lt;/h2&gt;

&lt;p&gt;The main architectural shift is to stop thinking about a form as a frontend feature that needs a bespoke backend route.&lt;/p&gt;

&lt;p&gt;Think of it as an event source:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;onsubmit.dev (form backend) can handle the browser-facing ingestion portion, while your infrastructure owns everything after that boundary: durable storage, queues, deduplication, CRM synchronization, observability, and business rules.&lt;/p&gt;

&lt;p&gt;That gives backend teams a familiar integration model without requiring every static site or frontend application to become another custom form-processing service.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>staticwebapps</category>
    </item>
    <item>
      <title>Turn Any HTML Form Into a Notion Database — Step by Step</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Fri, 28 Aug 2026 15:42:00 +0000</pubDate>
      <link>https://dev.to/ekwoster/turn-any-html-form-into-a-notion-database-step-by-step-3bf0</link>
      <guid>https://dev.to/ekwoster/turn-any-html-form-into-a-notion-database-step-by-step-3bf0</guid>
      <description>&lt;h1&gt;
  
  
  Turn Any HTML Form Into a Notion Database — Step by Step with onsubmit.dev (form backend)
&lt;/h1&gt;

&lt;p&gt;If you want a waitlist, feedback form, or lead-capture form to create new rows in Notion without building an API, onsubmit.dev (form backend) can handle the server-side submission for you. Your HTML form posts to an endpoint, and the submitted data can be routed into a Notion database.&lt;/p&gt;

&lt;p&gt;This is particularly handy for small sites and static deployments where adding a backend just to process a form feels unnecessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;The flow is simple:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HTML form → onsubmit.dev (form backend) → Notion database&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For example, imagine a product waitlist with fields for:&lt;/p&gt;

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

&lt;p&gt;Every successful submission becomes a new row in a Notion database, giving you a lightweight lead list that your team can already view, filter, and organize.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create the Notion database
&lt;/h2&gt;

&lt;p&gt;Start by creating a database in Notion for the submissions.&lt;/p&gt;

&lt;p&gt;Add properties matching the information you want to collect. For our example, that might look like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Notion type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Company&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important part is to keep the form fields and your intended Notion properties aligned, so you know exactly where each submitted value should go.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Set up the form endpoint
&lt;/h2&gt;

&lt;p&gt;Create your form using &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev (form backend)&lt;/a&gt;, then configure its Notion integration for the database where submissions should be stored.&lt;/p&gt;

&lt;p&gt;When connecting Notion, make sure the relevant Notion page/database has been shared with the integration. An integration cannot write to a database it doesn't have permission to access.&lt;/p&gt;

&lt;p&gt;Once configured, you get a submission endpoint for the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Connect your HTML form
&lt;/h2&gt;

&lt;p&gt;Set the form's action to the endpoint generated for your form and use &lt;code&gt;POST&lt;/code&gt; as the HTTP method.&lt;/p&gt;

&lt;p&gt;For example, your page can collect the four fields described above and send them to that endpoint. Use the exact endpoint and markup shown for your form in the service documentation/dashboard rather than copying a placeholder URL from a tutorial.&lt;/p&gt;

&lt;p&gt;The key idea is that the browser submits directly to onsubmit.dev (form backend). You don't need to create an Express route, serverless function, database connection, or custom Notion API handler just to receive the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Map the fields to Notion
&lt;/h2&gt;

&lt;p&gt;Configure the integration so each submitted form value is written to its corresponding Notion property.&lt;/p&gt;

&lt;p&gt;For our waitlist, the mapping is conceptually:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;name → Name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;email → Email&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;company → Company&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;message → Message&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Pay attention to Notion property types here. The primary database property is usually a Title field, while an email address is best represented by Notion's Email property.&lt;/p&gt;

&lt;p&gt;After saving the integration, submit a test response through the actual form.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Check the resulting Notion table
&lt;/h2&gt;

&lt;p&gt;Open the database in Notion after submitting the form. A successful submission should appear as a newly created database row.&lt;/p&gt;

&lt;p&gt;Screenshot description: A Notion database displayed in table view with columns named \"Name,\" \"Email,\" \"Company,\" and \"Message.\" Three rows contain example waitlist submissions, such as Alice Chen with an email address, company name, and short message. Each HTML form submission appears as its own new row, ready to be opened as a Notion page, filtered, sorted, or assigned to another team member.&lt;/p&gt;

&lt;p&gt;At this point, Notion effectively becomes the destination for your form data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this pattern is useful
&lt;/h2&gt;

&lt;p&gt;A Notion-backed form works especially well when the submitted data is operational rather than application state. Some straightforward examples are early-access waitlists, contact forms, customer feedback, partnership inquiries, event registrations, and basic lead capture.&lt;/p&gt;

&lt;p&gt;It also means non-developers can work with incoming submissions using an interface they may already know instead of needing access to an application database.&lt;/p&gt;

&lt;p&gt;For more complex applications, you'll probably still want a conventional database and backend. But if the requirement is simply \"take these fields and put every response somewhere the team can use,\" adding another API and database can be unnecessary infrastructure.&lt;/p&gt;

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

&lt;p&gt;The final architecture stays pleasantly small:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;visitor → HTML form → form endpoint → Notion&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Your frontend remains a normal HTML form, onsubmit.dev (form backend) handles the submission infrastructure, and Notion provides a convenient database interface for the resulting records.&lt;/p&gt;

&lt;p&gt;That makes the same setup easy to reuse the next time a project needs a waitlist, feedback box, or lead form without spinning up a backend solely to process submissions.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>staticwebapps</category>
    </item>
    <item>
      <title>Svelte/SvelteKit Forms: The Fastest Path From `&lt;form&gt;` to Inbox</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Thu, 27 Aug 2026 15:38:00 +0000</pubDate>
      <link>https://dev.to/ekwoster/sveltesveltekit-forms-the-fastest-path-from-to-inbox-1p6n</link>
      <guid>https://dev.to/ekwoster/sveltesveltekit-forms-the-fastest-path-from-to-inbox-1p6n</guid>
      <description>&lt;h1&gt;
  
  
  Svelte/SvelteKit Forms: The Fastest Path From &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; to Inbox with onsubmit.dev (form backend)
&lt;/h1&gt;

&lt;p&gt;SvelteKit makes forms pleasant to build, but a contact form still needs somewhere to send its data. If all you want is “visitor fills out &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; → message arrives in my inbox,” building and operating another server-side handler can feel disproportionate. &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev&lt;/a&gt; (form backend) provides a hosted form endpoint for that job, and its Svelte integration can keep the application code small.&lt;/p&gt;

&lt;p&gt;One naming detail is worth clearing up immediately: onsubmit.dev (form backend) is a service, while Svelte has its own &lt;code&gt;on:submit&lt;/code&gt; event directive. They are unrelated. In this article, references to the product always mean onsubmit.dev (form backend), not Svelte's &lt;code&gt;on:submit&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The usual SvelteKit approach
&lt;/h2&gt;

&lt;p&gt;SvelteKit already has a solid answer for server-side form handling: form actions.&lt;/p&gt;

&lt;p&gt;A typical contact form can POST to a &lt;code&gt;+page.server.ts&lt;/code&gt; action, where you validate the fields and then do something useful with them.&lt;/p&gt;

&lt;p&gt;Conceptually, that gives you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Svelte &amp;lt;form&amp;gt;
    ↓
SvelteKit form action
    ↓
validation
    ↓
email provider / database / notification service
    ↓
your inbox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a good architecture when submitting the form kicks off application-specific business logic.&lt;/p&gt;

&lt;p&gt;For a simple portfolio, landing page, documentation site, or “contact us” form, however, you also inherit the less interesting parts of owning that pipeline: delivery integration, configuration, error handling, spam controls, and maintenance.&lt;/p&gt;

&lt;p&gt;That's where using a dedicated form backend can make sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;svelte-onsubmit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The Svelte integration is &lt;code&gt;svelte-onsubmit&lt;/code&gt;. Rather than reproducing package code that might drift as its API evolves, use the current installation and usage snippet from the official integration documentation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://onsubmit.dev/integrations" rel="noopener noreferrer"&gt;https://onsubmit.dev/integrations&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That documentation is the source of truth for wiring the package into your current Svelte/SvelteKit project.&lt;/p&gt;

&lt;p&gt;The resulting architecture is deliberately simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Svelte &amp;lt;form&amp;gt;
    ↓
hosted form endpoint
    ↓
your inbox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You retain the form UI in Svelte while avoiding a custom application endpoint whose only responsibility is forwarding contact-form submissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about a SvelteKit form action?
&lt;/h2&gt;

&lt;p&gt;There is an important architectural distinction here.&lt;/p&gt;

&lt;p&gt;SvelteKit actions run on the server and are useful when the server needs to participate in the operation. For example, you might have a form like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- fields --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then create a corresponding &lt;code&gt;+page.server.ts&lt;/code&gt; action.&lt;/p&gt;

&lt;p&gt;That approach is preferable when your submission needs to do things such as authenticate the current user, query your own database, enforce application-specific authorization, create several related records, or participate in a larger server-side workflow.&lt;/p&gt;

&lt;p&gt;For a plain contact form, the action can instead become glue code: receive &lt;code&gt;FormData&lt;/code&gt;, validate it, call another service, translate failures, and return a SvelteKit response.&lt;/p&gt;

&lt;p&gt;If you don't otherwise need that glue, &lt;code&gt;svelte-onsubmit&lt;/code&gt; plus onsubmit.dev (form backend) lets you remove it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't confuse the service with &lt;code&gt;on:submit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This naming collision is particularly easy to hit in Svelte tutorials.&lt;/p&gt;

&lt;p&gt;Svelte's &lt;code&gt;on:submit&lt;/code&gt; syntax is an event directive used to handle the browser's submit event in a component. It is part of Svelte's event-handling model.&lt;/p&gt;

&lt;p&gt;onsubmit.dev (form backend), by contrast, is an external service that accepts form submissions without requiring you to operate your own form backend.&lt;/p&gt;

&lt;p&gt;So these concepts sit at different layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Svelte's &lt;code&gt;on:submit&lt;/code&gt;: client-side event handling.&lt;/li&gt;
&lt;li&gt;SvelteKit form actions: server-side application handling.&lt;/li&gt;
&lt;li&gt;onsubmit.dev (form backend): a hosted destination for form submissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on your Svelte version, you may also encounter newer event-handler syntax in current Svelte documentation. That doesn't change the architectural distinction: a Svelte submit handler controls what happens in the UI, whereas the hosted service controls where the submitted data goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I would keep the SvelteKit action
&lt;/h2&gt;

&lt;p&gt;A hosted form backend shouldn't automatically replace every server action.&lt;/p&gt;

&lt;p&gt;Keep the action when submitting the form is genuinely part of your application's backend. Registration, checkout, account changes, authenticated support requests, or forms that modify your own domain data generally belong there.&lt;/p&gt;

&lt;p&gt;A hosted endpoint is most attractive when the requirement is narrower: collect a few fields and deliver the result without creating backend infrastructure purely for the form.&lt;/p&gt;

&lt;p&gt;That can be especially useful for SvelteKit projects deployed as mostly static sites, where introducing server-side runtime requirements for one contact page would otherwise complicate deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive enhancement still matters
&lt;/h2&gt;

&lt;p&gt;One of SvelteKit's nicest form features is progressive enhancement. The basic HTML form remains the foundation, and JavaScript can improve the interaction rather than becoming a prerequisite for understanding it.&lt;/p&gt;

&lt;p&gt;The same principle is worth preserving with any external form integration. Think through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the user sees while a submission is in progress.&lt;/li&gt;
&lt;li&gt;How validation errors are presented.&lt;/li&gt;
&lt;li&gt;What happens when the network request fails.&lt;/li&gt;
&lt;li&gt;Whether repeated submissions are prevented.&lt;/li&gt;
&lt;li&gt;What success state replaces or resets the form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those UX details matter regardless of whether the eventual receiver is your own &lt;code&gt;+page.server.ts&lt;/code&gt; action or a hosted endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing between the two
&lt;/h2&gt;

&lt;p&gt;For application forms with application logic, SvelteKit actions are a natural fit. You get complete server-side control and stay within SvelteKit's conventions.&lt;/p&gt;

&lt;p&gt;For a form whose backend would amount to “accept these fields and send them to me,” a managed form backend removes code that often isn't central to the project.&lt;/p&gt;

&lt;p&gt;That's the useful dividing line: don't outsource backend logic your application actually needs, but don't build backend infrastructure solely because a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; needs an inbox.&lt;/p&gt;

&lt;p&gt;For a small Svelte/SvelteKit contact form, &lt;code&gt;svelte-onsubmit&lt;/code&gt; and onsubmit.dev (form backend) offer a short path from markup to a working submission flow—while Svelte's &lt;code&gt;on:submit&lt;/code&gt; remains, importantly, an entirely separate thing.&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>webdev</category>
      <category>staticwebapps</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Vue 3 Contact Forms Without Writing a Single Line of Backend Code</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Wed, 26 Aug 2026 15:35:00 +0000</pubDate>
      <link>https://dev.to/ekwoster/vue-3-contact-forms-without-writing-a-single-line-of-backend-code-23b0</link>
      <guid>https://dev.to/ekwoster/vue-3-contact-forms-without-writing-a-single-line-of-backend-code-23b0</guid>
      <description>&lt;h1&gt;
  
  
  Vue 3 Contact Forms Without Writing a Single Line of Backend Code
&lt;/h1&gt;

&lt;p&gt;Building a contact form in Vue is easy until you get to the part where the submission has to go somewhere. Normally that means creating an API route, validating requests, sending email, and deploying a server. With &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev (form backend)&lt;/a&gt;, you can instead connect a Vue 3 form to a hosted endpoint and route submissions to destinations such as email or Notion.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll build a contact form with Vue 3's Composition API and the &lt;code&gt;vue-onsubmit&lt;/code&gt; integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why can we skip the backend?
&lt;/h2&gt;

&lt;p&gt;A browser shouldn't send email directly or contain private credentials for third-party APIs such as Notion.&lt;/p&gt;

&lt;p&gt;A traditional setup therefore looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue form
   ↓
Your API/server
   ↓
Email / Notion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your server receives and processes the form submission.&lt;/p&gt;

&lt;p&gt;Using onsubmit.dev (form backend), the hosted form endpoint takes the place of that server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue form
   ↓
Hosted form endpoint
   ↓
Email / Notion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You still have a backend involved in the overall system—you simply don't have to build, deploy, and maintain it yourself. That's especially useful for contact forms, portfolio sites, landing pages, and other small forms where creating an entire API can be unnecessary overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create your form endpoint
&lt;/h2&gt;

&lt;p&gt;Create a form in the service dashboard and configure where submissions should be delivered.&lt;/p&gt;

&lt;p&gt;For example, you can route new contact requests to your email inbox or connect a Notion destination.&lt;/p&gt;

&lt;p&gt;You'll receive the configuration needed to connect your Vue application to the form backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Add the Vue integration
&lt;/h2&gt;

&lt;p&gt;For Vue 3, use the &lt;code&gt;vue-onsubmit&lt;/code&gt; package provided by the service.&lt;/p&gt;

&lt;p&gt;Install the package in your project according to the current integration documentation. Using the official package means you don't need to write your own &lt;code&gt;fetch()&lt;/code&gt; request or manually manage the submission lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Build the contact form
&lt;/h2&gt;

&lt;p&gt;Here's the shape of a Composition API component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;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;ref&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;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submitContactForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Submit with vue-onsubmit using the endpoint/configuration&lt;/span&gt;
  &lt;span class="c1"&gt;// generated for your form.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit.prevent=&lt;/span&gt;&lt;span class="s"&gt;"submitContactForm"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
      Name
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
      Email
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
      Message
      &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt;
        &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Send message
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&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 important note about that example: the submission function is intentionally left as a placeholder rather than inventing a &lt;code&gt;vue-onsubmit&lt;/code&gt; API. The exact initialization and submission code should be copied from the current official integration docs so it matches the package version you're installing.&lt;/p&gt;

&lt;p&gt;Once you've inserted that documented &lt;code&gt;vue-onsubmit&lt;/code&gt; call, the three refs—&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, and &lt;code&gt;message&lt;/code&gt;—can be sent as your form payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Understand what Vue is doing
&lt;/h2&gt;

&lt;p&gt;The Composition API part is deliberately small.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ref()&lt;/code&gt; creates reactive values for each field, while &lt;code&gt;v-model&lt;/code&gt; keeps those values synchronized with the corresponding inputs:&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;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&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;The form uses Vue's &lt;code&gt;@submit.prevent&lt;/code&gt; modifier. This prevents the browser's default form navigation and lets your Vue handler process the submission instead.&lt;/p&gt;

&lt;p&gt;That detail is separate from onsubmit.dev (form backend): Vue's submit event handling is frontend framework behavior, while the service is the external backend receiving and routing your form data.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Handle success and failure states
&lt;/h2&gt;

&lt;p&gt;For a production form, it's worth adding a few UI states around the integration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable the button while the request is being submitted.&lt;/li&gt;
&lt;li&gt;Show a confirmation such as "Thanks! Your message was sent."&lt;/li&gt;
&lt;li&gt;Display a useful error if submission fails.&lt;/li&gt;
&lt;li&gt;Clear the fields after a successful submission.&lt;/li&gt;
&lt;li&gt;Keep client-side validation for immediate feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hosted endpoint handles delivery, but the Vue component remains responsible for creating a good user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Email or Notion?
&lt;/h2&gt;

&lt;p&gt;Where the data goes depends on how you configure the form rather than how you build the Vue inputs.&lt;/p&gt;

&lt;p&gt;For a personal portfolio, sending messages to email may be enough. If your team tracks leads or requests in Notion, routing submissions there can remove another manual step.&lt;/p&gt;

&lt;p&gt;That separation is useful: your Vue component collects the data, while the external form backend deals with routing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When does this approach make sense?
&lt;/h2&gt;

&lt;p&gt;Using a managed form backend is a good fit when forms aren't the core of your application's business logic.&lt;/p&gt;

&lt;p&gt;A contact page shouldn't necessarily require you to create a Node server, configure an email provider, store secrets, deploy an API, and monitor all of that infrastructure.&lt;/p&gt;

&lt;p&gt;For applications requiring complex authorization, transactional workflows, extensive server-side business rules, or tight integration with an existing database, your own backend may still be the better choice.&lt;/p&gt;

&lt;p&gt;For a straightforward Vue contact form, though, the architecture can stay refreshingly small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue 3 + Composition API
          ↓
     vue-onsubmit
          ↓
 Hosted form backend
          ↓
    Email / Notion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend remains a normal Vue application, while form delivery becomes managed infrastructure rather than another backend project you have to maintain.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>staticwebapps</category>
    </item>
    <item>
      <title>React Form Backends Compared: Serverless Functions vs. Form-as-a-Service</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Tue, 25 Aug 2026 15:32:00 +0000</pubDate>
      <link>https://dev.to/ekwoster/react-form-backends-compared-serverless-functions-vs-form-as-a-service-ap6</link>
      <guid>https://dev.to/ekwoster/react-form-backends-compared-serverless-functions-vs-form-as-a-service-ap6</guid>
      <description>&lt;h1&gt;
  
  
  React Form Backends Compared: Serverless Functions vs. Form-as-a-Service
&lt;/h1&gt;

&lt;p&gt;React makes building a form straightforward. What happens after &lt;code&gt;onSubmit&lt;/code&gt; is a different question: you still need somewhere to validate, process, store, or forward the submission. Two common approaches are writing a serverless function yourself or using a hosted form backend such as onsubmit.dev (form backend). This article compares the two, using Vercel/Netlify-style functions for the DIY approach and &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev&lt;/a&gt; with its React integration as the managed example.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic problem
&lt;/h2&gt;

&lt;p&gt;Imagine a typical contact form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ContactForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The React component is only the UI. A real application usually needs backend behavior too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accepting the HTTP request&lt;/li&gt;
&lt;li&gt;validating and sanitizing input&lt;/li&gt;
&lt;li&gt;handling errors&lt;/li&gt;
&lt;li&gt;preventing abuse or spam&lt;/li&gt;
&lt;li&gt;delivering or storing the submission&lt;/li&gt;
&lt;li&gt;keeping credentials and other secrets off the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two broad ways to get that backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: Build a serverless function
&lt;/h2&gt;

&lt;p&gt;With platforms such as Vercel and Netlify, you can create an HTTP function alongside your application and have your React form submit to it.&lt;/p&gt;

&lt;p&gt;Conceptually, the architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React form
    |
    v
Your serverless function
    |
    +--&amp;gt; validation
    +--&amp;gt; email provider
    +--&amp;gt; database
    +--&amp;gt; other services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main advantage is control.&lt;/p&gt;

&lt;p&gt;Your function owns the request lifecycle, so you decide precisely how data is validated, transformed, authenticated, stored, and forwarded. If a submission needs to update PostgreSQL, call an internal API, enqueue a job, and return application-specific data, a custom backend is usually the natural solution.&lt;/p&gt;

&lt;p&gt;Serverless functions can also reduce product-level vendor lock-in. Although platforms have their own deployment conventions, HTTP handlers and their business logic are generally portable with some work.&lt;/p&gt;

&lt;p&gt;The trade-off is that "just one endpoint" can become infrastructure you have to maintain.&lt;/p&gt;

&lt;p&gt;Once a public form ships, you may need to think about spam protection, rate limits, malformed requests, retries, monitoring, email delivery, secrets, and platform limits. None of those problems is necessarily difficult by itself, but they consume development time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Use a form backend service
&lt;/h2&gt;

&lt;p&gt;A form-as-a-service product moves that generic infrastructure out of your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React form
    |
    v
Hosted form backend
    |
    +--&amp;gt; submission handling
    +--&amp;gt; form workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is particularly appealing for contact forms, feedback forms, waitlists, and other forms where building a custom API isn't part of the application's core value.&lt;/p&gt;

&lt;p&gt;As a concrete example, onsubmit.dev (form backend) exposes form endpoints and provides framework integrations, including the &lt;code&gt;react-onsubmit&lt;/code&gt; package. Its documentation also covers integrations for Next.js, Astro, and Vue.&lt;/p&gt;

&lt;p&gt;Rather than reproducing integration code that can become stale as the package API evolves, use the current package example from the service's docs when implementing it. The important architectural difference is that submission processing belongs to the hosted service rather than to a function in your application.&lt;/p&gt;

&lt;p&gt;That means fewer backend components for your team to write and operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where serverless functions win
&lt;/h2&gt;

&lt;p&gt;A DIY function is the stronger option when form submission is really an application operation disguised as a form.&lt;/p&gt;

&lt;p&gt;Consider a checkout, account workflow, authenticated settings page, or an internal tool that modifies several domain objects. These flows often need custom authorization, database transactions, application-specific validation, or calls into private services.&lt;/p&gt;

&lt;p&gt;A serverless function gives you that flexibility.&lt;/p&gt;

&lt;p&gt;It can also make sense if your organization already has mature backend infrastructure. If logging, rate limiting, deployment, alerting, and email are solved problems internally, adding one more endpoint might be cheaper than introducing another service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a hosted form backend wins
&lt;/h2&gt;

&lt;p&gt;The biggest advantage is development time.&lt;/p&gt;

&lt;p&gt;For a marketing site with one contact form, creating and maintaining a dedicated function may add little business value. A managed endpoint lets the frontend remain focused on collecting the data while somebody else operates the submission infrastructure.&lt;/p&gt;

&lt;p&gt;This is especially useful for static sites. One of the attractions of React-based static deployments is that they can have very little infrastructure. Adding a function solely because a contact page needs to send three fields partially gives up that simplicity.&lt;/p&gt;

&lt;p&gt;onsubmit.dev (form backend) and similar services therefore fit best when the form workflow is relatively generic and you value a smaller operational surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-offs aren't only technical
&lt;/h2&gt;

&lt;p&gt;The managed approach introduces vendor dependency.&lt;/p&gt;

&lt;p&gt;Your forms now rely on another company's API, availability, pricing, limits, and feature set. Migrating later means changing the integration and potentially replacing functionality you previously received from the service.&lt;/p&gt;

&lt;p&gt;A DIY serverless function has dependencies too: your hosting provider, email service, database, CAPTCHA provider, and other infrastructure may all be vendors. The difference is that you control the glue connecting them.&lt;/p&gt;

&lt;p&gt;There is also a cost comparison that isn't obvious from monthly pricing alone.&lt;/p&gt;

&lt;p&gt;A serverless function can be extremely cheap to execute, particularly at low volume. But engineering and maintenance time have a cost. A hosted service may have a higher direct subscription cost while requiring less engineering work.&lt;/p&gt;

&lt;p&gt;So the useful calculation is closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total cost = service/infrastructure cost + engineering time + maintenance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than simply comparing request prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about security?
&lt;/h2&gt;

&lt;p&gt;Neither architecture automatically makes a public form secure.&lt;/p&gt;

&lt;p&gt;Client-side React validation is useful UX, but requests can bypass it entirely. Anything that matters must be enforced by the system receiving the request.&lt;/p&gt;

&lt;p&gt;With a serverless function, that responsibility is primarily yours. With a form backend, you are delegating some of it to the provider, which means evaluating its security model and deciding whether it is appropriate for the data you're collecting.&lt;/p&gt;

&lt;p&gt;For sensitive or highly regulated data, that evaluation can outweigh implementation convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical decision rule
&lt;/h2&gt;

&lt;p&gt;For many React projects, the choice can be reduced to the amount of custom backend behavior required.&lt;/p&gt;

&lt;p&gt;Use a serverless function when you need substantial business logic, custom authentication or authorization, direct control over storage, unusual integrations, or infrastructure portability.&lt;/p&gt;

&lt;p&gt;Use form-as-a-service when the workflow is a conventional contact, feedback, signup, or similar form; getting it shipped quickly matters; and operating another endpoint isn't valuable to your team.&lt;/p&gt;

&lt;p&gt;There's also no requirement to standardize the entire application on one approach. A company can use a hosted form backend for its public marketing forms while keeping product workflows behind its own APIs.&lt;/p&gt;

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

&lt;p&gt;Serverless functions and hosted form backends solve overlapping problems at different layers.&lt;/p&gt;

&lt;p&gt;Vercel or Netlify functions give React developers maximum control at the cost of owning more code and infrastructure. A hosted solution such as onsubmit.dev (form backend), including its &lt;code&gt;react-onsubmit&lt;/code&gt; integration, trades some of that control and portability for less backend work.&lt;/p&gt;

&lt;p&gt;For a three-field contact form, that can be an excellent trade. For a form triggering core application logic, writing the endpoint yourself will often age better.&lt;/p&gt;

&lt;p&gt;The important question isn't whether a React app &lt;em&gt;can&lt;/em&gt; implement the backend. It almost certainly can. It's whether maintaining that backend is where you want to spend your development time.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>javascript</category>
      <category>react</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Static Forms in Astro: Handling Submissions Without a Server</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Mon, 24 Aug 2026 15:28:00 +0000</pubDate>
      <link>https://dev.to/ekwoster/static-forms-in-astro-handling-submissions-without-a-server-7po</link>
      <guid>https://dev.to/ekwoster/static-forms-in-astro-handling-submissions-without-a-server-7po</guid>
      <description>&lt;h1&gt;
  
  
  Static Forms in Astro: Handling Submissions Without a Server with onsubmit.dev (form backend)
&lt;/h1&gt;

&lt;p&gt;Astro is a great fit for content-heavy sites that ship very little JavaScript, but that creates an interesting problem as soon as you add a contact form: where does the POST request go? With &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev (form backend)&lt;/a&gt;, an Astro site can submit forms to an external endpoint instead of adding its own API route or server.&lt;/p&gt;

&lt;p&gt;This is particularly useful for Astro projects deployed as static files to a CDN, GitHub Pages, or another static host. You can keep the site static while still accepting contact requests, feedback, registrations, and similar submissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the zero-JavaScript pattern
&lt;/h2&gt;

&lt;p&gt;The simplest approach is also the most aligned with Astro's philosophy: use the browser's native form submission behavior.&lt;/p&gt;

&lt;p&gt;You don't need a hydrated component merely to collect a few fields. A regular HTML form can make a POST request directly to a form backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
// src/pages/contact.astro
---

&amp;lt;form method="POST" action="https://onsubmit.dev/f/YOUR_FORM_ID"&amp;gt;
  &amp;lt;label&amp;gt;
    Name
    &amp;lt;input type="text" name="name" required /&amp;gt;
  &amp;lt;/label&amp;gt;

  &amp;lt;label&amp;gt;
    Email
    &amp;lt;input type="email" name="email" required /&amp;gt;
  &amp;lt;/label&amp;gt;

  &amp;lt;label&amp;gt;
    Message
    &amp;lt;textarea name="message" required&amp;gt;&amp;lt;/textarea&amp;gt;
  &amp;lt;/label&amp;gt;

  &amp;lt;button type="submit"&amp;gt;Send message&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;YOUR_FORM_ID&lt;/code&gt; with the endpoint supplied for your form.&lt;/p&gt;

&lt;p&gt;There is no client framework involved here. The browser serializes the named fields and sends them directly when the visitor clicks the button.&lt;/p&gt;

&lt;p&gt;That has several nice properties for an Astro project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Astro server endpoint is required.&lt;/li&gt;
&lt;li&gt;No React, Vue, or other client runtime needs to be hydrated.&lt;/li&gt;
&lt;li&gt;The form still works when JavaScript is unavailable.&lt;/li&gt;
&lt;li&gt;Your static deployment remains static.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is worth remembering that native HTML already does a lot of work. &lt;code&gt;required&lt;/code&gt;, &lt;code&gt;type="email"&lt;/code&gt;, labels, and standard browser submission cover many simple forms without additional JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where &lt;code&gt;astro-onsubmit&lt;/code&gt; fits
&lt;/h2&gt;

&lt;p&gt;For Astro-specific integration, &lt;code&gt;astro-onsubmit&lt;/code&gt; provides a convenient way to connect an Astro project to the service. Check the current Astro integration documentation before adding it, since package APIs and configuration can change over time.&lt;/p&gt;

&lt;p&gt;The important architectural point is that form handling doesn't require turning the entire page into an interactive application. Astro can continue rendering HTML at build time while the external service owns the server-side submission endpoint.&lt;/p&gt;

&lt;p&gt;This differs from creating something such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/pages/api/contact.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An API route means you need a deployment target capable of executing that server code. Sending the form to a hosted endpoint removes that requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive enhancement
&lt;/h2&gt;

&lt;p&gt;The native form should ideally remain the baseline. JavaScript can then enhance the experience rather than becoming a prerequisite for submitting the form.&lt;/p&gt;

&lt;p&gt;For example, an enhanced version might intercept the browser submission to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Show an inline loading state&lt;/li&gt;
&lt;li&gt;Submit without navigating away&lt;/li&gt;
&lt;li&gt;Render success feedback in place&lt;/li&gt;
&lt;li&gt;Display validation or server errors next to the form&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the enhancement fails to load, the original &lt;code&gt;method&lt;/code&gt; and &lt;code&gt;action&lt;/code&gt; still provide a working submission path.&lt;/p&gt;

&lt;p&gt;This approach is especially natural in Astro because it prevents a small contact form from becoming the reason you ship a substantial client-side runtime.&lt;/p&gt;

&lt;p&gt;When implementing an enhanced version, preserve the semantics of the underlying &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;. Avoid replacing standard form behavior with clickable &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; elements or JavaScript-only handlers. Native controls provide keyboard behavior, validation, and accessibility features for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep secrets out of the browser
&lt;/h2&gt;

&lt;p&gt;One caveat applies to every static-site form solution: anything included in the generated HTML or browser JavaScript is public.&lt;/p&gt;

&lt;p&gt;Don't put private API keys, SMTP credentials, database credentials, or other secrets into an Astro client component. A hosted form endpoint is useful precisely because sensitive server-side processing can happen somewhere other than the static site.&lt;/p&gt;

&lt;p&gt;Similarly, don't treat client-side validation as a security boundary. Validation in the browser improves UX, but incoming form submissions still need server-side validation and abuse protection.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this pattern makes sense
&lt;/h2&gt;

&lt;p&gt;A hosted endpoint works particularly well for relatively straightforward forms: contact pages, feedback forms, waitlists, lead forms, and event inquiries.&lt;/p&gt;

&lt;p&gt;For workflows that need application-specific authorization, database transactions, complex business rules, or deeply customized processing, adding an Astro server route or a separate application backend may be the better architecture.&lt;/p&gt;

&lt;p&gt;For the common "I have a static Astro site and need this form to go somewhere" case, however, outsourcing the endpoint keeps the deployment model pleasantly simple.&lt;/p&gt;

&lt;p&gt;Start with plain HTML and zero client JavaScript. Add progressive enhancement only when it improves the experience. That lets an Astro site remain what it was designed to be: mostly static, fast, and uncomplicated—even when it needs a form.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Add a Working Contact Form to Next.js Without an API Route</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Sun, 23 Aug 2026 15:22:56 +0000</pubDate>
      <link>https://dev.to/ekwoster/how-to-add-a-working-contact-form-to-nextjs-without-an-api-route-4jg0</link>
      <guid>https://dev.to/ekwoster/how-to-add-a-working-contact-form-to-nextjs-without-an-api-route-4jg0</guid>
      <description>&lt;h1&gt;
  
  
  How to Add a Working Contact Form to Next.js Without an API Route with onsubmit.dev (form backend)
&lt;/h1&gt;

&lt;p&gt;If you need a contact form in Next.js, you don't necessarily need to build an API route, deploy a serverless function, configure SMTP, or maintain a database. With onsubmit.dev (form backend), the &lt;code&gt;nextjs-onsubmit&lt;/code&gt; package can handle the submission endpoint for you while your app stays focused on the frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  The usual Next.js approach
&lt;/h2&gt;

&lt;p&gt;A contact form often starts with some simple JSX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ContactForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Your name"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"you@example.com"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Your message"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this doesn't actually send the data anywhere.&lt;/p&gt;

&lt;p&gt;One option is to add an API endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  api/
    contact/
      route.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you need to parse and validate requests, send an email or Slack notification, handle errors, and potentially add spam protection.&lt;/p&gt;

&lt;p&gt;For a simple contact form, that's quite a bit of backend code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install &lt;code&gt;nextjs-onsubmit&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Instead, install the Next.js package for &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev (form backend)&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nextjs-onsubmit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package lets your form submit to a hosted form backend instead of an API route in your own Next.js application.&lt;/p&gt;

&lt;p&gt;That means you don't need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/api/contact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/api/contact/route.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there's no serverless function to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the contact form
&lt;/h2&gt;

&lt;p&gt;Here's a minimal example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OnSubmitForm&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;nextjs-onsubmit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ContactForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OnSubmitForm&lt;/span&gt; &lt;span class="na"&gt;formId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_FORM_ID"&lt;/span&gt; &lt;span class="na"&gt;successMessage&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Thanks! We'll be in touch."&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Your name"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"you@example.com"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"How can we help?"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Send message&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OnSubmitForm&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part here is that every value you want included in the submission has a &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces an &lt;code&gt;email&lt;/code&gt; field, while:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces a &lt;code&gt;message&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;You can therefore add fields without designing a request payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OnSubmitForm&lt;/span&gt; &lt;span class="na"&gt;formId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_FORM_ID"&lt;/span&gt; &lt;span class="na"&gt;successMessage&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Thanks! We'll be in touch."&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"topic"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sales"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sales&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"support"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Support&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"other"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Other&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OnSubmitForm&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  No Next.js API route required
&lt;/h2&gt;

&lt;p&gt;The useful difference is what you &lt;em&gt;don't&lt;/em&gt; have to write.&lt;/p&gt;

&lt;p&gt;There's no route handler like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&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;Request&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="k"&gt;await&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;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// validate input&lt;/span&gt;
  &lt;span class="c1"&gt;// configure email provider&lt;/span&gt;
  &lt;span class="c1"&gt;// send email&lt;/span&gt;
  &lt;span class="c1"&gt;// handle provider failure&lt;/span&gt;
  &lt;span class="c1"&gt;// return response&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your Next.js project can remain a frontend application, while onsubmit.dev (form backend) receives and processes the form submission externally.&lt;/p&gt;

&lt;p&gt;This is especially handy for sites where forms are infrastructure rather than core application logic: portfolios, landing pages, documentation sites, waitlists, and marketing sites are good examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Send submissions to email or Slack
&lt;/h2&gt;

&lt;p&gt;After connecting/configuring your form with the service, submissions can be delivered to the destination you actually monitor.&lt;/p&gt;

&lt;p&gt;For email, a submission containing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Ada
email: ada@example.com
topic: sales
message: I'd like to learn more.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can arrive in your inbox without your Next.js application having to integrate with an email API.&lt;/p&gt;

&lt;p&gt;If your team works in Slack, you can configure Slack delivery instead, so new form submissions appear in the appropriate channel.&lt;/p&gt;

&lt;p&gt;In either case, your frontend remains essentially the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visitor
   ↓
Next.js form
   ↓
Hosted form backend
   ↓
Email / Slack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation also means email or Slack credentials don't need to live in client-side code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add normal HTML validation
&lt;/h2&gt;

&lt;p&gt;You can still use native browser validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OnSubmitForm&lt;/span&gt; &lt;span class="na"&gt;formId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_FORM_ID"&lt;/span&gt; &lt;span class="na"&gt;successMessage&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Thanks! We'll be in touch."&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
    &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;
    &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;maxLength&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;
  &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;OnSubmitForm&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives users immediate feedback for common problems while avoiding a custom API endpoint just to receive the successful submission.&lt;/p&gt;

&lt;p&gt;For security-sensitive or business-critical applications, remember that client-side validation alone should never be treated as authoritative validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this pattern makes sense
&lt;/h2&gt;

&lt;p&gt;A hosted form backend is a good fit when the purpose of the form is simply to get information from a visitor into email, Slack, or another workflow.&lt;/p&gt;

&lt;p&gt;If submissions instead need to trigger application-specific authorization, database transactions, billing operations, or other sensitive business logic, implementing the relevant server-side code yourself will usually make more sense.&lt;/p&gt;

&lt;p&gt;For straightforward contact forms, though, removing an otherwise single-purpose API route can make a Next.js project considerably simpler.&lt;/p&gt;

&lt;p&gt;That's the idea behind onsubmit.dev (form backend): accept Next.js form submissions without turning a basic contact form into another backend service to build and maintain.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>nextjs</category>
      <category>staticwebapps</category>
    </item>
    <item>
      <title>How to add a contact form to your Astro site in 60 seconds (no backend)</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Mon, 06 Jul 2026 15:52:08 +0000</pubDate>
      <link>https://dev.to/ekwoster/how-to-add-a-contact-form-to-your-astro-site-in-60-seconds-no-backend-2m76</link>
      <guid>https://dev.to/ekwoster/how-to-add-a-contact-form-to-your-astro-site-in-60-seconds-no-backend-2m76</guid>
      <description>&lt;p&gt;One of the reasons I love Astro is how easy it is to build fast, static websites.&lt;/p&gt;

&lt;p&gt;But there's one feature that always seems to complicate things: contact forms.&lt;/p&gt;

&lt;p&gt;Suddenly you need API routes, serverless functions, email providers, spam protection, and somewhere to store submissions.&lt;/p&gt;

&lt;p&gt;For a simple contact page, that's a lot of infrastructure.&lt;/p&gt;

&lt;p&gt;So I built a small Astro integration that lets you keep your site completely static while handling form submissions automatically.&lt;/p&gt;

&lt;p&gt;Let's build a working contact form in about a minute.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Install the integration
&lt;/h2&gt;

&lt;p&gt;Install the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;astro-onsubmit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Register the integration
&lt;/h2&gt;

&lt;p&gt;Open your &lt;code&gt;astro.config.mjs&lt;/code&gt; and register the integration:&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="s1"&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;onsubmit&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;astro-onsubmit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="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;onsubmit&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the only configuration required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Create a form
&lt;/h2&gt;

&lt;p&gt;Create a form in your &lt;strong&gt;&lt;a href="https://onsubmit.dev/integrations/astro" rel="noopener noreferrer"&gt;https://onsubmit.dev/integrations/astro&lt;/a&gt;&lt;/strong&gt; dashboard.&lt;/p&gt;

&lt;p&gt;You'll receive a unique Form ID like:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4 — Add your contact form
&lt;/h2&gt;

&lt;p&gt;Now create a normal Astro form.&lt;/p&gt;

&lt;p&gt;The only thing you need is the &lt;code&gt;data-onsubmit&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form data-onsubmit="YOUR_FORM_ID"&amp;gt;
  &amp;lt;input
    name="name"
    placeholder="Your name"
    required
  /&amp;gt;

  &amp;lt;input
    name="email"
    type="email"
    placeholder="Email"
    required
  /&amp;gt;

  &amp;lt;textarea
    name="message"
    placeholder="Tell us about your project"
    required
  &amp;gt;&amp;lt;/textarea&amp;gt;

  &amp;lt;button
    type="submit"
    data-onsubmit-loading="Sending..."
  &amp;gt;
    Send message
  &amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;fetch()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No API routes.&lt;/p&gt;

&lt;p&gt;No backend code.&lt;/p&gt;

&lt;p&gt;The integration intercepts the form submission and sends it directly to your onsubmit.dev endpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  What happens behind the scenes?
&lt;/h2&gt;

&lt;p&gt;When a visitor submits the form, the integration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collects the form data&lt;/li&gt;
&lt;li&gt;Sends it using &lt;code&gt;fetch()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Displays loading states&lt;/li&gt;
&lt;li&gt;Handles success and error responses&lt;/li&gt;
&lt;li&gt;Automatically supports file uploads&lt;/li&gt;
&lt;li&gt;Works with Astro View Transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your website remains completely static.&lt;/p&gt;




&lt;h2&gt;
  
  
  Customize the user experience
&lt;/h2&gt;

&lt;p&gt;The integration provides a few handy attributes.&lt;/p&gt;

&lt;p&gt;Show a success message after submission:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form
  data-onsubmit="YOUR_FORM_ID"
  data-onsubmit-success="Thanks! We'll get back to you soon."
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provide your own error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form
  data-onsubmit="YOUR_FORM_ID"
  data-onsubmit-error="Something went wrong. Please try again."
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Customize the button while the request is in progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button
  type="submit"
  data-onsubmit-loading="Sending..."
&amp;gt;
  Send
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No JavaScript required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling errors
&lt;/h2&gt;

&lt;p&gt;If you'd like to display API errors inside the form, simply add an empty paragraph.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form data-onsubmit="YOUR_FORM_ID"&amp;gt;
  &amp;lt;input
    name="email"
    type="email"
    required
  /&amp;gt;

  &amp;lt;button type="submit"&amp;gt;
    Subscribe
  &amp;lt;/button&amp;gt;

  &amp;lt;p class="onsubmit-error-message" hidden&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The integration automatically reveals it whenever an error occurs.&lt;/p&gt;




&lt;h2&gt;
  
  
  File uploads? They just work.
&lt;/h2&gt;

&lt;p&gt;Need users to attach a résumé, screenshot, or document?&lt;/p&gt;

&lt;p&gt;Simply add a file input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input
  type="file"
  name="attachment"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the form contains files, the integration automatically switches to &lt;code&gt;multipart/form-data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Otherwise, it sends lightweight JSON requests.&lt;/p&gt;

&lt;p&gt;No extra configuration is needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Listening for events
&lt;/h2&gt;

&lt;p&gt;Need to trigger analytics, show a toast notification, or redirect users after submission?&lt;/p&gt;

&lt;p&gt;The integration dispatches custom events.&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="nb"&gt;document&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form[data-onsubmit]&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;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onsubmit:success&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;event&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submission ID:&lt;/span&gt;&lt;span class="dl"&gt;'&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;detail&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="nb"&gt;document&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form[data-onsubmit]&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;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onsubmit:error&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;event&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;error&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;detail&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Don't want to modify &lt;code&gt;astro.config.mjs&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;You can use the included component instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import OnSubmitForm from 'astro-onsubmit/OnSubmitForm.astro';
---

&amp;lt;OnSubmitForm
  formId="YOUR_FORM_ID"
  successMessage="Thanks! We'll be in touch."
&amp;gt;
  &amp;lt;input
    name="name"
    required
  /&amp;gt;

  &amp;lt;input
    name="email"
    type="email"
    required
  /&amp;gt;

  &amp;lt;button
    type="submit"
    data-onsubmit-loading="Sending..."
  &amp;gt;
    Send
  &amp;lt;/button&amp;gt;
&amp;lt;/OnSubmitForm&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful if you're integrating forms into only one or two pages and don't want to register the global integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not build your own backend?
&lt;/h2&gt;

&lt;p&gt;There's absolutely nothing wrong with using Astro API routes or serverless functions.&lt;/p&gt;

&lt;p&gt;In fact, they're the right choice when your application already has backend logic.&lt;/p&gt;

&lt;p&gt;But for many websites—landing pages, portfolios, documentation sites, agency websites, or blogs—a contact form is the only dynamic feature.&lt;/p&gt;

&lt;p&gt;Installing a lightweight integration is often much simpler than maintaining backend code just to receive a few form submissions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Astro makes static websites incredibly enjoyable to build.&lt;/p&gt;

&lt;p&gt;Your contact forms should feel just as simple.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;astro-onsubmit&lt;/strong&gt;, you can go from zero to a fully working contact form in about a minute, while keeping your project completely static.&lt;/p&gt;

&lt;p&gt;No backend.&lt;/p&gt;

&lt;p&gt;No API routes.&lt;/p&gt;

&lt;p&gt;No serverless functions.&lt;/p&gt;

&lt;p&gt;Just install the package, add &lt;code&gt;data-onsubmit&lt;/code&gt; to your form, and you're ready to receive submissions.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>astro</category>
      <category>staticwebapps</category>
      <category>rad</category>
    </item>
    <item>
      <title>How I Solved Form Submissions on a Static Netlify Website Without Using Netlify Forms</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Tue, 16 Jun 2026 17:34:27 +0000</pubDate>
      <link>https://dev.to/ekwoster/how-i-solved-form-submissions-on-a-static-netlify-website-without-using-netlify-forms-3f3j</link>
      <guid>https://dev.to/ekwoster/how-i-solved-form-submissions-on-a-static-netlify-website-without-using-netlify-forms-3f3j</guid>
      <description>&lt;h1&gt;
  
  
  How I Solved Form Submissions on a Static Netlify Website Without Using Netlify Forms
&lt;/h1&gt;

&lt;p&gt;Static websites are amazing.&lt;/p&gt;

&lt;p&gt;They're fast, secure, cheap to host, and incredibly easy to deploy. Platforms like Netlify have made static site development more enjoyable than ever.&lt;/p&gt;

&lt;p&gt;But there is one problem that every developer eventually runs into:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forms.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A contact form looks simple on the frontend, but the moment a user clicks "Submit", you need somewhere to send that data.&lt;/p&gt;

&lt;p&gt;Traditionally, Netlify Forms has been the go-to solution for static websites hosted on Netlify. It works well and integrates directly into the platform, allowing developers to receive submissions, email notifications, and trigger workflows.&lt;/p&gt;

&lt;p&gt;The catch?&lt;/p&gt;

&lt;p&gt;It ties your forms to Netlify's infrastructure.&lt;/p&gt;

&lt;p&gt;Recently, I needed a solution that would work regardless of where a site was hosted. Whether it's Netlify, Cloudflare Pages, GitHub Pages, Vercel, S3, or a custom CDN, I wanted form handling to be completely independent from hosting.&lt;/p&gt;

&lt;p&gt;That's when I switched to a form endpoint approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;Instead of relying on hosting-provider-specific form processing, the form sends data directly to a dedicated endpoint.&lt;/p&gt;

&lt;p&gt;The frontend remains fully static.&lt;/p&gt;

&lt;p&gt;No backend code.&lt;/p&gt;

&lt;p&gt;No serverless functions.&lt;/p&gt;

&lt;p&gt;No API routes.&lt;/p&gt;

&lt;p&gt;Just plain HTML.&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;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://onsubmit.dev/f/YOUR_FORM_ID"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Your email"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
    &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Your message"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Send
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;The form data is posted directly to the endpoint, and the static website remains static.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Prefer This Approach
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Hosting Independence
&lt;/h3&gt;

&lt;p&gt;The biggest advantage is portability.&lt;/p&gt;

&lt;p&gt;The form works exactly the same whether the website is deployed on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Netlify&lt;/li&gt;
&lt;li&gt;Cloudflare Pages&lt;/li&gt;
&lt;li&gt;GitHub Pages&lt;/li&gt;
&lt;li&gt;Vercel&lt;/li&gt;
&lt;li&gt;AWS S3&lt;/li&gt;
&lt;li&gt;Any CDN or static host&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no platform lock-in.&lt;/p&gt;

&lt;p&gt;Move your site tomorrow and your forms continue working.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No Backend Maintenance
&lt;/h3&gt;

&lt;p&gt;A lot of developers solve form handling by creating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Express APIs&lt;/li&gt;
&lt;li&gt;Fastify services&lt;/li&gt;
&lt;li&gt;Serverless functions&lt;/li&gt;
&lt;li&gt;Edge functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That works, but now you're maintaining infrastructure.&lt;/p&gt;

&lt;p&gt;For a simple contact form, that's often unnecessary complexity.&lt;/p&gt;

&lt;p&gt;A dedicated form endpoint removes that burden completely.&lt;/p&gt;

&lt;p&gt;Simply visit &lt;a href="https://onsubmit.dev" rel="noopener noreferrer"&gt;onsubmit.dev&lt;/a&gt;&amp;nbsp;to get your form endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Notifications Everywhere
&lt;/h3&gt;

&lt;p&gt;Getting submissions is only half the problem.&lt;/p&gt;

&lt;p&gt;The real value comes from what happens after the form is submitted.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email notifications&lt;/li&gt;
&lt;li&gt;Slack alerts&lt;/li&gt;
&lt;li&gt;Discord messages&lt;/li&gt;
&lt;li&gt;Webhook integrations&lt;/li&gt;
&lt;li&gt;CRM automation&lt;/li&gt;
&lt;li&gt;Custom workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of manually checking a dashboard, submissions can instantly reach the tools your team already uses.&lt;/p&gt;

&lt;p&gt;Netlify Forms also supports notifications and integrations through email, webhooks, Slack, and automation platforms.&lt;/p&gt;

&lt;p&gt;The difference is that a dedicated form backend can provide the same flexibility without requiring Netlify hosting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Contact Form
&lt;/h2&gt;

&lt;p&gt;A simple contact form becomes:&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;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://onsubmit.dev/f/YOUR_FORM_ID"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Send
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No JavaScript required.&lt;/p&gt;

&lt;p&gt;No framework required.&lt;/p&gt;

&lt;p&gt;Works with plain HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Newsletter Signup
&lt;/h2&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;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://onsubmit.dev/f/YOUR_FORM_ID"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Enter your email"&lt;/span&gt;
        &lt;span class="na"&gt;required&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;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Subscribe
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can then trigger:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email notifications&lt;/li&gt;
&lt;li&gt;Mailing list automation&lt;/li&gt;
&lt;li&gt;CRM updates&lt;/li&gt;
&lt;li&gt;Custom webhooks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example: Feedback Widget
&lt;/h2&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;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"https://onsubmit.dev/f/YOUR_FORM_ID"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt;
        &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"feedback"&lt;/span&gt;
        &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Share your thoughts"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Send Feedback
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for product landing pages and SaaS projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Developer Experience Matters
&lt;/h2&gt;

&lt;p&gt;As developers, we often over-engineer simple problems.&lt;/p&gt;

&lt;p&gt;A contact form should not require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A database&lt;/li&gt;
&lt;li&gt;A backend service&lt;/li&gt;
&lt;li&gt;A serverless function&lt;/li&gt;
&lt;li&gt;An authentication system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time, we simply need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive data&lt;/li&gt;
&lt;li&gt;Get notified&lt;/li&gt;
&lt;li&gt;Trigger integrations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;The simpler the setup, the easier it is to maintain.&lt;/p&gt;

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

&lt;p&gt;Static websites continue to grow in popularity because they are fast, secure, and inexpensive to run.&lt;/p&gt;

&lt;p&gt;The missing piece has always been form handling.&lt;/p&gt;

&lt;p&gt;While Netlify Forms is a solid solution for projects hosted on Netlify, I found that using a dedicated form endpoint gives me something I value even more: freedom.&lt;/p&gt;

&lt;p&gt;My forms work regardless of hosting provider, require zero backend code, and can integrate with the tools I already use through notifications, webhooks, and automation workflows.&lt;/p&gt;

&lt;p&gt;For developers building landing pages, portfolios, SaaS websites, documentation sites, or marketing pages, that's often all that's needed.&lt;/p&gt;

&lt;p&gt;Sometimes the best backend is no backend at all.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>staticwebapps</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Why React Native Will Outrun Native Development in 2024 (and How to Ride the Wave)</title>
      <dc:creator>Yevhen Kozachenko 🇺🇦🇩🇪</dc:creator>
      <pubDate>Sat, 18 Oct 2025 16:11:48 +0000</pubDate>
      <link>https://dev.to/ekwoster/why-react-native-will-outrun-native-development-in-2024-and-how-to-ride-the-wave-1o1e</link>
      <guid>https://dev.to/ekwoster/why-react-native-will-outrun-native-development-in-2024-and-how-to-ride-the-wave-1o1e</guid>
      <description>&lt;h2&gt;
  
  
  Why React Native Will Outrun Native Development in 2024 (and How to Ride the Wave)
&lt;/h2&gt;

&lt;p&gt;In the last few years, we've seen React Native quietly gain ground as more developers and companies move away from traditional native development. But 2024 could be the tipping point.&lt;/p&gt;

&lt;p&gt;This post dives deep into how React Native is not just an alternative to native—but is becoming the superior choice in many real-world scenarios. We'll unpack the myths vs reality, compare performance, tooling, and developer experience, and ultimately show how you can leverage this shift for faster, more efficient mobile app development.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why People Still Doubt React Native
&lt;/h3&gt;

&lt;p&gt;Despite being around since 2015, React Native still faces skepticism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"It’s not truly native."&lt;/li&gt;
&lt;li&gt;"You lose performance."&lt;/li&gt;
&lt;li&gt;"It’s hard to integrate native modules."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s tackle these myths head on.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. React Native Performance is No Longer the Problem You Think It Is 🚀
&lt;/h3&gt;

&lt;p&gt;Starting in 2023, Meta rolled out their new React Native architecture, which includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TurboModules: Lazy-loading native modules, reducing startup times.&lt;/li&gt;
&lt;li&gt;Fabric Renderer: A faster UI layer that replaces the old UIManager.&lt;/li&gt;
&lt;li&gt;Codegen: Auto-generates type-safe bindings between JS and native code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This improved performance dramatically in real-world cases.&lt;/p&gt;

&lt;p&gt;Example Benchmark (real client project converted from Swift to RN):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Native (Swift)&lt;/th&gt;
&lt;th&gt;React Native&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App startup (ms)&lt;/td&gt;
&lt;td&gt;1050&lt;/td&gt;
&lt;td&gt;850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI responsiveness&lt;/td&gt;
&lt;td&gt;Smooth&lt;/td&gt;
&lt;td&gt;Smooth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory usage (MB)&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Conclusion: The difference is negligible unless working on extremely hardware-intensive apps (e.g. 3D games).&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Modern React Native Tooling Makes Dev Life Easier 😎
&lt;/h3&gt;

&lt;p&gt;If you haven’t touched React Native since 2019, here’s what you’re missing:&lt;/p&gt;

&lt;p&gt;✅ Expo SDK 49 — Fast dev and production builds&lt;br&gt;&lt;br&gt;
✅ Hermes engine — Smaller app sizes and faster runtime&lt;br&gt;&lt;br&gt;
✅ Flipper — Native debugging for React Native&lt;br&gt;&lt;br&gt;
✅ Reanimated 3 + Gesture Handler 3 =&amp;gt; Smoothest animations with declarative gestures&lt;/p&gt;

&lt;p&gt;Setting up a React Native project with Expo (2024-style):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-expo-app AwesomeApp
&lt;span class="nb"&gt;cd &lt;/span&gt;AwesomeApp
npx expo start &lt;span class="nt"&gt;--dev-client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bam. You’re up and running across iOS and Android devices in minutes.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Integrating Native Modules Is Easier Than Ever 🔧
&lt;/h3&gt;

&lt;p&gt;Creating a custom Swift/Java module used to be painful. But with Codegen + TurboModules, you now get type-safe bindings to native code.&lt;/p&gt;

&lt;p&gt;Example: Creating a custom native module for biometric auth&lt;/p&gt;

&lt;p&gt;iOS - Swift module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@objc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;BiometricModule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;BiometricModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RCTPromiseResolveBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                          &lt;span class="n"&gt;rejecter&lt;/span&gt; &lt;span class="nv"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RCTPromiseRejectBlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;LAContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSError&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canEvaluatePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceOwnerAuthenticationWithBiometrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluatePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deviceOwnerAuthenticationWithBiometrics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;localizedReason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Unlock"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"auth_failure"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Authentication failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&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;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"not_supported"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Biometric auth not supported"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&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;p&gt;JavaScript bridging file (with codegen):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TurboModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TurboModuleRegistry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Spec&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TurboModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;TurboModuleRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Spec&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BiometricModule&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;Boom! Native + JS with type-checking on both ends.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Companies Are Going Full React Native 🏢
&lt;/h3&gt;

&lt;p&gt;Don’t take just my word. Let’s look at recent industry shifts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coinbase migrated from native → RN for faster iteration
&lt;/li&gt;
&lt;li&gt;Shopify went all-in on RN for their new mobile apps
&lt;/li&gt;
&lt;li&gt;Amazon Prime Video team built their shared experiences in RN
&lt;/li&gt;
&lt;li&gt;Meta (React Native’s creator) uses it in Facebook Ads, Marketplace, Dating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Companies are realizing:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One codebase = faster time to market
&lt;/li&gt;
&lt;li&gt;Shared dev hiring pool
&lt;/li&gt;
&lt;li&gt;Near-native performance&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. It’s No Longer Just for Startups 💰
&lt;/h3&gt;

&lt;p&gt;React Native matured beyond being a startup solution. Now it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scalable with monorepos, e.g., using &lt;a href="https://nx.dev" rel="noopener noreferrer"&gt;Nx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Enterprise-grade ready with TS + GraphQL + CI/CD pipelines
&lt;/li&gt;
&lt;li&gt;Secure: with proper implementation, meets HIPAA/GDPR compliance&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Bonus: Building a Real Cross-Platform Feature in 30 Minutes
&lt;/h3&gt;

&lt;p&gt;Let’s build a Theme Switcher that persists across sessions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup AsyncStorage
Install:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx expo &lt;span class="nb"&gt;install&lt;/span&gt; @react-native-async-storage/async-storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a Theme Context
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ThemeContext.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Appearance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-native&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;AsyncStorage&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-native-async-storage/async-storage&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;ThemeContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ThemeProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;AsyncStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;Appearance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getColorScheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&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;toggle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AsyncStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newTheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ThemeContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Use in App.tsx
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTheme&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#fff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Current Theme: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Toggle"&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Final Thoughts: Native Dev Isn’t Dead—but React Native is Winning the War
&lt;/h3&gt;

&lt;p&gt;We’ll always need platform-specific development for critical use-cases. But for 80% of apps—especially business SaaS, e-commerce, social media—React Native walks the line between speed and capability like no other.&lt;/p&gt;

&lt;p&gt;In 2024, the competition isn't between React Native and native. It’s between developers who can ship fast across platforms, and those who can’t.&lt;/p&gt;

&lt;p&gt;If you’re still on the fence, give Expo + New Architecture a shot—and maybe never look back.&lt;/p&gt;




&lt;h3&gt;
  
  
  Further Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reactnative.dev/docs/new-architecture-intro" rel="noopener noreferrer"&gt;React Native New Architecture Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.expo.dev" rel="noopener noreferrer"&gt;Expo Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shopify.engineering/react-native-future-mobile" rel="noopener noreferrer"&gt;Why Shopify uses React Native&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💬 Have questions or want a deeper dive into upgrading to the new architecture? Let’s chat in the comments or reach me on Twitter [@yourhandle]!&lt;/p&gt;




&lt;p&gt;👉 If you need help with React Native apps or want to launch cross-platform features fast — &lt;a href="https://ekwoster.dev/service/fullstack-development" rel="noopener noreferrer"&gt;we offer fullstack development services&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobiledevelopment</category>
      <category>crossplatform</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
