<?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: Yuto Tanaka</title>
    <description>The latest articles on DEV Community by Yuto Tanaka (@yutotanaka).</description>
    <link>https://dev.to/yutotanaka</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%2F4151440%2F28512d68-7c50-4412-baf3-4d4dbcb39fc7.jpg</url>
      <title>DEV Community: Yuto Tanaka</title>
      <link>https://dev.to/yutotanaka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yutotanaka"/>
    <language>en</language>
    <item>
      <title>How to A/B Test Two Landing Pages Using Campaign Links</title>
      <dc:creator>Yuto Tanaka</dc:creator>
      <pubDate>Fri, 16 Oct 2026 00:01:00 +0000</pubDate>
      <link>https://dev.to/yutotanaka/how-to-ab-test-two-landing-pages-using-campaign-links-p44</link>
      <guid>https://dev.to/yutotanaka/how-to-ab-test-two-landing-pages-using-campaign-links-p44</guid>
      <description>&lt;p&gt;You have two landing pages and one campaign link. You want to know which page converts better without sending half your traffic to a page you haven't tested. The usual answer is an A/B test, but the mechanics of routing a single campaign link to two destinations and then measuring the result are less obvious.&lt;/p&gt;

&lt;p&gt;This article covers a practical setup: a stable campaign URL that redirects visitors to variant A or B, assigns each visitor consistently, and passes the variant through to your analytics. The code is small; the discipline is in consistent assignment and clean measurement.&lt;/p&gt;

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

&lt;p&gt;A campaign link is the URL you put in ads, emails, social posts, or QR codes. If you change that URL for every experiment, you lose attribution and break links that people have already shared. So the campaign link should stay stable, and the decision about which landing page to show should happen behind it.&lt;/p&gt;

&lt;p&gt;The simplest architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Campaign link (short or long) points to a redirect endpoint, e.g. &lt;code&gt;https://example.com/campaign&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The redirect endpoint assigns a variant and sends a &lt;code&gt;302&lt;/code&gt; to the matching landing page.&lt;/li&gt;
&lt;li&gt;The landing page records the variant and reports conversions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This keeps one public URL while you test behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assignment: random once, then sticky
&lt;/h2&gt;

&lt;p&gt;The most common mistake is assigning a variant on every page load. If the same visitor sees page A, then page B on refresh, your data is noisy and the user experience is confusing.&lt;/p&gt;

&lt;p&gt;You want &lt;em&gt;sticky&lt;/em&gt; assignment: once a visitor is assigned to A or B, they see that variant for the duration of the test. A first-party cookie is the simplest way to do this.&lt;/p&gt;

&lt;p&gt;On first visit, pick a variant at random and set a cookie. On later visits, read the cookie and use the same variant.&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;LANDING_PAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/landing-a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/landing-b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getVariant&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;cookieHeader&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookieHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/ab_variant=&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;a|b&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&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;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;match&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="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&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;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&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;url&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;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/campaign&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getVariant&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LANDING_PAGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;variant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&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;no-store&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;existingCookie&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;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;existingCookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ab_variant=&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set-Cookie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`ab_variant=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; Path=/; Max-Age=2592000; SameSite=Lax`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;302&lt;/code&gt; (temporary) instead of &lt;code&gt;301&lt;/code&gt; (permanent) so browsers and proxies do not cache the redirect permanently.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Cache-Control: no-store&lt;/code&gt; on the redirect response so a CDN does not serve the same variant to everyone.&lt;/li&gt;
&lt;li&gt;Appending &lt;code&gt;?variant=&lt;/code&gt; to the destination makes the variant available to client-side analytics without reading cookies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you use cookies, check your privacy requirements. A first-party, non-tracking cookie that only stores &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;b&lt;/code&gt; is usually fine, but it should be covered by your cookie policy. If you cannot use cookies, a fallback is hashing a combination of IP and user agent, but that is less stable and can misassign visitors on shared networks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passing the variant to analytics
&lt;/h2&gt;

&lt;p&gt;The A/B test is only as good as its measurement. You need to know which variant each conversion belongs to.&lt;/p&gt;

&lt;p&gt;The easiest approach is to read the &lt;code&gt;variant&lt;/code&gt; query parameter on the landing page and send it to your analytics tool as a custom dimension or event property.&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;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&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;variant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;variant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;analytics&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;Landing Page 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="nx"&gt;variant&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;analytics&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;Signup&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;variant&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use Google Analytics 4, set the variant as a user property or include it in the event parameters. In Plausible or Fathom, you can pass it as a custom property. The exact API depends on your tool, but the idea is the same: every event tied to the experiment carries the variant.&lt;/p&gt;

&lt;p&gt;Do not rely on URL parameters alone if users can navigate away and come back. Either set the cookie on the redirect (as above) or set a cookie on the landing page from the query parameter, so the variant persists across page views.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Client-side redirects cause flicker.&lt;/strong&gt; If you assign the variant with JavaScript after the page loads, the visitor may briefly see the wrong page. Server-side or edge redirects happen before any HTML is rendered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bots skew the results.&lt;/strong&gt; Crawlers and monitoring tools often follow redirects. You can exclude known bot user agents from assignment, or simply filter them out in analytics. Either way, know that your raw traffic numbers include non-humans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stopping too early.&lt;/strong&gt; A/B tests need enough conversions to distinguish a real difference from noise. If you have low traffic, you may need to run the test for weeks. Use a sample size calculator before you start, and don't peek at the results every hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing during a seasonality spike.&lt;/strong&gt; If you run variant A during a sale and variant B after it, you are measuring time, not the landing page. Run both variants simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting &lt;code&gt;noindex&lt;/code&gt;.&lt;/strong&gt; Landing pages used in a test should have &lt;code&gt;&amp;lt;meta name="robots" content="noindex"&amp;gt;&lt;/code&gt; so search engines don't index two near-duplicate pages. The redirect endpoint should also not be indexed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the campaign link shareable
&lt;/h2&gt;

&lt;p&gt;A redirect endpoint like &lt;code&gt;https://example.com/campaign&lt;/code&gt; is fine, but it may be long, especially if you add tracking parameters. A short link can make the campaign URL easier to paste into ads, social bios, or QR codes.&lt;/p&gt;

&lt;p&gt;You can point a short link at your assignment endpoint. For example, a short link service like &lt;a href="https://linkbit.live/um47iyx" rel="noopener noreferrer"&gt;LinkBit&lt;/a&gt; can give you a compact URL that redirects to &lt;code&gt;https://example.com/campaign&lt;/code&gt;. The edge function still handles variant assignment; the short link just makes the entry point cleaner to share.&lt;/p&gt;

&lt;p&gt;One caution: if a short link service offers its own A/B or split-testing feature, don't layer it on top of your own assignment logic unless you have verified how the two interact. Duplicate splitting can break sticky assignment and make the data harder to interpret. Keep the experiment logic in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to measure
&lt;/h2&gt;

&lt;p&gt;For each variant, track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visitors assigned&lt;/strong&gt;: how many people entered the test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversions&lt;/strong&gt;: the action you care about (signup, purchase, demo request).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversion rate&lt;/strong&gt;: conversions divided by visitors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to conversion&lt;/strong&gt;: useful if the decision is not immediate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare conversion rates, but also check whether the variants differ in bounce rate, scroll depth, or other engagement signals. A page that converts slightly better but has a much higher bounce rate may not be the winner you think it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;A/B testing two landing pages with one campaign link comes down to three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A stable campaign URL that redirects to the variant.&lt;/li&gt;
&lt;li&gt;Sticky, random assignment stored in a cookie.&lt;/li&gt;
&lt;li&gt;Clean analytics that carry the variant through to conversion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code is a few dozen lines. The harder part is resisting the urge to change the test midway, and making sure your measurement is as reliable as your routing.&lt;/p&gt;

&lt;h1&gt;
  
  
  ABTesting #WebDevelopment #EdgeComputing #Analytics #LandingPages
&lt;/h1&gt;

</description>
      <category>analytics</category>
      <category>marketing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Where to Place a QR Code on a Brochure—and What to Write Beside It</title>
      <dc:creator>Yuto Tanaka</dc:creator>
      <pubDate>Wed, 30 Sep 2026 06:30:00 +0000</pubDate>
      <link>https://dev.to/yutotanaka/where-to-place-a-qr-code-on-a-brochure-and-what-to-write-beside-it-31pc</link>
      <guid>https://dev.to/yutotanaka/where-to-place-a-qr-code-on-a-brochure-and-what-to-write-beside-it-31pc</guid>
      <description>&lt;p&gt;A brochure QR code needs to answer two questions before anyone scans it: &lt;em&gt;What will I get, and can my phone read this comfortably?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Placement solves the second problem. The text beside the code solves the first. For developers preparing brochures for a product demo, conference booth, or technical workshop, treat both as part of the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put the code where the reader decides to act
&lt;/h2&gt;

&lt;p&gt;Choose the destination first, then place the QR code beside the content that makes that destination useful.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reader’s next step&lt;/th&gt;
&lt;th&gt;Suitable placement&lt;/th&gt;
&lt;th&gt;Suggested label&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Try a product&lt;/td&gt;
&lt;td&gt;Beside the demo description or final call to action&lt;/td&gt;
&lt;td&gt;“Scan to open the interactive demo”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow an implementation guide&lt;/td&gt;
&lt;td&gt;Beside the setup overview&lt;/td&gt;
&lt;td&gt;“Scan for the installation guide”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Register for a workshop&lt;/td&gt;
&lt;td&gt;Beside the date, venue, and registration details&lt;/td&gt;
&lt;td&gt;“Scan to register for the workshop”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View a code sample&lt;/td&gt;
&lt;td&gt;Beside the relevant technical example&lt;/td&gt;
&lt;td&gt;“Scan to view the sample repository”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A back panel works well for a general next step, such as booking a demo. It is less useful when the code explains a specific diagram buried inside the brochure. In that case, keep the diagram, label, and code together.&lt;/p&gt;

&lt;p&gt;For a tri-fold brochure, check the &lt;strong&gt;actual folded reading sequence&lt;/strong&gt;, not just the order of panels in your design file. Print a rough copy and fold it. Confirm that the code appears on the panel you intended and that its explanation is visible at the same time.&lt;/p&gt;

&lt;p&gt;Avoid putting essential next steps on a panel readers are unlikely to unfold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it away from folds, trim edges, and visual noise
&lt;/h2&gt;

&lt;p&gt;Give the QR code its own stable, uncluttered area.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not straddle a fold. A crease can distort the pattern or make it difficult to hold flat.&lt;/li&gt;
&lt;li&gt;Keep it inside the printer’s safe area. Trim tolerances vary; use the print provider’s specifications rather than assuming a universal margin.&lt;/li&gt;
&lt;li&gt;Avoid photographs and textured backgrounds. A solid light background with dark modules is a reliable starting point.&lt;/li&gt;
&lt;li&gt;Do not crowd it with labels or icons. The blank border is part of the code’s scanning requirements.&lt;/li&gt;
&lt;li&gt;Consider how people hold the brochure. A code at a lower corner may be covered by a thumb.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a standard QR Code, preserve a quiet zone at least &lt;strong&gt;four modules wide on every side&lt;/strong&gt;. A module is one small square in the pattern. Keep text, borders, and artwork outside that zone.&lt;/p&gt;

&lt;p&gt;The quiet zone and the print safe area serve different purposes: one helps decoding; the other protects content from trimming. You need both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Size the printed code, not just the image file
&lt;/h2&gt;

&lt;p&gt;There is no single minimum physical size that guarantees scanning. Reliability depends on pattern density, print quality, contrast, lighting, camera performance, and scanning distance.&lt;/p&gt;

&lt;p&gt;For a handheld brochure, a code around &lt;strong&gt;25–30 mm square, plus its quiet zone&lt;/strong&gt;, can be a useful prototype size. It is a starting point to test, not a production guarantee. Dense codes or longer scanning distances may need more space.&lt;/p&gt;

&lt;p&gt;Developer-friendly checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep the encoded value reasonably short. Longer values can require more modules, making each module smaller at the same printed size.&lt;/li&gt;
&lt;li&gt;Export as vector artwork when your print workflow supports it. SVG or a suitable vector PDF avoids raster scaling artifacts.&lt;/li&gt;
&lt;li&gt;If using a raster image, avoid blur and smoothing. Check it at its final placed size; nominal DPI alone does not guarantee readable modules.&lt;/li&gt;
&lt;li&gt;Do not assume higher error correction fixes poor layout. It can increase symbol density and does not replace adequate size, contrast, or a quiet zone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you choose a shortened destination, a service such as &lt;a href="https://linkbit.live/1ahfmsr" rel="noopener noreferrer"&gt;LinkBit&lt;/a&gt; can shorten the URL before you generate the QR code. That may reduce the encoded payload, but it also introduces a redirect dependency. Confirm the final destination and plan for the link to remain usable throughout the brochure’s lifetime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the benefit beside the code
&lt;/h2&gt;

&lt;p&gt;“Scan me” describes the interaction, not the reason to do it.&lt;/p&gt;

&lt;p&gt;Use this pattern:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scan to + specific action or resource + relevant expectation.&lt;/strong&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;“Scan to try the browser demo—no installation required.” Use only if that is true.&lt;/li&gt;
&lt;li&gt;“Scan for the Python quickstart and sample project.” Better than “Learn more” when those are the actual resources.&lt;/li&gt;
&lt;li&gt;“Scan to book a 15-minute technical walkthrough.” Appropriate when the destination is a booking page.&lt;/li&gt;
&lt;li&gt;“Scan to download the specification PDF.” Makes the destination format clear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mention friction when it matters: account requirements, paid registration, or a download rather than a webpage. A reader should not discover a materially different task after scanning.&lt;/p&gt;

&lt;p&gt;Keep the label next to the code, with enough separation to preserve the quiet zone. If scanning is optional, include a readable fallback address outside that zone. Use a genuine, working address—not an illustrative placeholder in the final artwork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the complete printed interaction
&lt;/h2&gt;

&lt;p&gt;A QR image that scans on a monitor has not passed a brochure test.&lt;/p&gt;

&lt;p&gt;Before approving the print run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Print at &lt;strong&gt;100% of the intended physical size&lt;/strong&gt;, without “fit to page” scaling.&lt;/li&gt;
&lt;li&gt;Fold the brochure and scan it while holding it naturally.&lt;/li&gt;
&lt;li&gt;Try more than one phone under ordinary indoor lighting.&lt;/li&gt;
&lt;li&gt;Check the finished stock or proof where possible; glossy reflections can interfere with scanning.&lt;/li&gt;
&lt;li&gt;Confirm that redirects resolve and the final page uses a valid HTTPS connection.&lt;/li&gt;
&lt;li&gt;Test the destination on mobile, including forms, downloads, and any login requirements.&lt;/li&gt;
&lt;li&gt;Verify that the label accurately describes what opens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you include several QR codes, give each a distinct purpose and label. For a single next step, one well-placed code is usually clearer than repeating it across every panel.&lt;/p&gt;

&lt;p&gt;The final check is simple: can someone understand the offer, scan without rearranging the brochure awkwardly, and reach the promised resource? If not, fix the placement, label, or destination before adding more visual styling.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
