<?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: The EasyFunnel team</title>
    <description>The latest articles on DEV Community by The EasyFunnel team (@easyfunnel).</description>
    <link>https://dev.to/easyfunnel</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%2F4189099%2F47a7c770-1816-4ef4-8608-f43cbb5bdd5b.png</url>
      <title>DEV Community: The EasyFunnel team</title>
      <link>https://dev.to/easyfunnel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/easyfunnel"/>
    <language>en</language>
    <item>
      <title>How to Measure Your Website's Conversion Rate Properly (Views, Starts, Submits) with ~30 Lines of JavaScript</title>
      <dc:creator>The EasyFunnel team</dc:creator>
      <pubDate>Fri, 16 Oct 2026 14:35:32 +0000</pubDate>
      <link>https://dev.to/easyfunnel/how-to-measure-your-websites-conversion-rate-properly-views-starts-submits-with-30-lines-of-3ioi</link>
      <guid>https://dev.to/easyfunnel/how-to-measure-your-websites-conversion-rate-properly-views-starts-submits-with-30-lines-of-3ioi</guid>
      <description>&lt;p&gt;Most "conversion rate" numbers on dashboards are wrong in one boring way: the denominator. People divide signups by &lt;em&gt;all&lt;/em&gt; sessions, by &lt;em&gt;pageviews&lt;/em&gt;, or by "visitors" from one tool and conversions from another. Then they wonder why the rate jumps around.&lt;/p&gt;

&lt;p&gt;This post covers how to define the funnel, instrument it with plain JavaScript, and compute a rate you can trust.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Pick one goal and one denominator
&lt;/h4&gt;

&lt;p&gt;Conversion rate = conversions ÷ visitors × 100.&lt;/p&gt;

&lt;p&gt;The formula is easy. The hard part is deciding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What counts as a conversion?&lt;/strong&gt; Pick one: a submitted form, a completed checkout, a booked call. Don't blend them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who counts as a visitor?&lt;/strong&gt; Unique visitors to &lt;em&gt;the page that holds the goal&lt;/em&gt; is usually the honest denominator. Sitewide sessions inflate it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: 10,000 visitors to a landing page and 250 signups gives 2.5%.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Split the form funnel into three events
&lt;/h4&gt;

&lt;p&gt;A single "submit" event hides where people drop. Track three:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;form_view&lt;/code&gt;: the form actually entered the viewport (not just page load).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;form_start&lt;/code&gt;: first interaction with any field.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;form_submit&lt;/code&gt;: a successful submit (after validation, not on click).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Zuko's 2025 benchmarks put view-to-submit abandonment around 55%, and contact forms convert about 9% of the people who view them. Without the view/start split you can't tell a "nobody sees it" problem from a "people start and quit" problem.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Instrument it
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Minimal form funnel tracking. Swap `track` for your analytics call.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;track&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;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g. gtag('event', event, props) or posthog.capture(event, props)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;instrumentForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;viewed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;viewed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;viewed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;track&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_view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&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="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;form&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;focusin&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;track&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_start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkValidity&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="nf"&gt;track&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_submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="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;querySelectorAll&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-track]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;instrumentForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;track&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;Mark forms with &lt;code&gt;&amp;lt;form data-track="contact"&amp;gt;&lt;/code&gt;. If you submit via &lt;code&gt;fetch&lt;/code&gt;, fire &lt;code&gt;form_submit&lt;/code&gt; in the success handler instead, so failed requests don't count.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Compute three rates, not one
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visibility:&lt;/strong&gt; form_view ÷ page visitors. If it's low, move the form up or make it reachable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start rate:&lt;/strong&gt; form_start ÷ form_view. If it's low, the offer or the first field is the problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completion:&lt;/strong&gt; form_submit ÷ form_start. If it's low, look at length, unclear fields or trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overall conversion:&lt;/strong&gt; form_submit ÷ page visitors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5. Sanity-check the math and size the upside
&lt;/h4&gt;

&lt;p&gt;Before arguing about a redesign, plug your visitors and conversions into a calculator to check the rate. Then see what a 10–25% lift would be worth. We built a free one with a what-if slider and no sign-up: &lt;a href="https://easyfunnel.co/tools/conversion-rate-calculator" rel="noopener noreferrer"&gt;conversion rate calculator&lt;/a&gt;. A spreadsheet works too. The point is to put a number on the upside before spending a sprint on it.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Common gotchas
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Bots and your own team inflate visitors, so filter internal IPs.&lt;/li&gt;
&lt;li&gt;Single-page apps: fire page-visitor events on route change, not just on load.&lt;/li&gt;
&lt;li&gt;Compare like periods (weekday vs weekday). Small sites need weeks of data before a change in rate means anything.&lt;/li&gt;
&lt;li&gt;Change one thing at a time so you know what moved the rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Wrap-up
&lt;/h4&gt;

&lt;p&gt;Define one goal, use the page's own visitors as the denominator, and split the form into view → start → submit. You'll know &lt;em&gt;which&lt;/em&gt; step leaks, and that tells you what to fix.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The EasyFunnel team. We build always-on lead capture for websites.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>analytics</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
