Landing page headline substitution based on ad campaign

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Landing Headline Substitution by Ad Campaign

Headline substitution (dynamic keyword insertion at site level) is a technique where H1 and subheadings automatically change to match the ad that brought the user. If person clicked ad "Website design for dentistry"—they land on page with heading "Website design for dentistry", not generic web studio landing.

Operating Principle

Ad system passes parameters in URL. Either standard UTM or custom parameters. Site reads parameters, finds correct headline in dictionary, and substitutes it in DOM before user can see original text.

Two approaches: parameter in URL directly contains text (unsafe—can be spoofed) or parameter contains key where server/client finds headline from predefined dictionary.

Headline Dictionary

// headline-map.js — campaign → headline mapping
export const headlineMap: Record<string, HeadlineSet> = {
  // Google Ads — by utm_campaign
  'dental-clinic-sites': {
    h1: 'Websites for dental clinics—turnkey in 14 days',
    h2: 'Online booking, patient portal, MIS integration',
    breadcrumb: 'Clinic websites',
  },
  'lawyer-sites': {
    h1: 'Websites for law firms',
    h2: 'Privacy, trust, consultation booking form',
    breadcrumb: 'Lawyer websites',
  },
  'ecom-landing': {
    h1: 'E-commerce from scratch—prototype to launch',
    h2: 'Catalog, cart, payment, shipping—all ready',
    breadcrumb: 'E-commerce stores',
  },

  // By utm_term (keyword)
  terms: {
    'create website': {
      h1: 'We'll create a website for your needs',
      h2: 'Calculate cost in 2 minutes',
    },
    'landing page development': {
      h1: 'Landing with 5%+ conversion',
      h2: 'Sells 24/7—while you focus on business',
    },
  },
};

Substitution Logic

interface HeadlineSet {
  h1?: string;
  h2?: string;
  breadcrumb?: string;
}

function resolveHeadlines(): HeadlineSet {
  const params = new URLSearchParams(location.search);
  const campaign = params.get('utm_campaign') ?? sessionStorage.getItem('utm_campaign');
  const term = params.get('utm_term') ?? sessionStorage.getItem('utm_term');

  // Save for next pages
  if (params.get('utm_campaign')) {
    sessionStorage.setItem('utm_campaign', params.get('utm_campaign')!);
    sessionStorage.setItem('utm_term', params.get('utm_term') ?? '');
  }

  // Priority: campaign > keyword > default
  if (campaign && headlineMap[campaign]) return headlineMap[campaign];

  if (term) {
    const termKey = Object.keys(headlineMap.terms ?? {})
      .find(k => term.toLowerCase().includes(k));
    if (termKey) return headlineMap.terms[termKey];
  }

  return {}; // don't change default text
}

function applyHeadlines(headlines: HeadlineSet): void {
  const map: [string, keyof HeadlineSet][] = [
    ['[data-headline="h1"]', 'h1'],
    ['[data-headline="h2"]', 'h2'],
    ['[data-headline="breadcrumb"]', 'breadcrumb'],
  ];

  map.forEach(([selector, key]) => {
    const el = document.querySelector(selector);
    if (el && headlines[key]) {
      el.textContent = headlines[key]!;
    }
  });
}

// Run—ideally inline in <head> to avoid flicker
const headlines = resolveHeadlines();
applyHeadlines(headlines);

HTML Markup

<!DOCTYPE html>
<html>
<head>
  <!-- Headline substitution script—before body render -->
  <script src="/js/headline-substitution.js"></script>
</head>
<body>
  <nav class="breadcrumb">
    <a href="/">Home</a> /
    <span data-headline="breadcrumb">Website development</span>
  </nav>

  <section class="hero">
    <h1 data-headline="h1">Website development for business</h1>
    <p data-headline="h2">Since 2010 — over 400 projects</p>
    <a href="/brief" class="btn-primary">Calculate cost</a>
  </section>
</body>
</html>

Script substitution must be synchronous in <head>, not defer/async—otherwise user sees original heading for millisecond before replacement (flash of original content). Script is small—1-2 KB, render blocking insignificant.

Google Ads Dynamic Keyword Insertion vs Headline Substitution

DKI in Google Ads inserts keyword into ad. Headline substitution on site—logical continuation: site "answers" in same language as ad. Connection works via utm_term:

Google Ads campaign creates URL template:

https://example.com/landing?utm_campaign=dental&utm_term={keyword}

Google auto-substitutes actual keyword into {keyword}. Site reads utm_term and shows corresponding headline.

Server-Side Substitution (No Flicker)

For SSR projects, server-side substitution is more reliable:

// LandingController.php
public function index(Request $request): Response
{
    $campaign = $request->get('utm_campaign') ?? $request->session()->get('utm_campaign');
    $term = $request->get('utm_term') ?? $request->session()->get('utm_term');

    if ($request->get('utm_campaign')) {
        $request->session()->put('utm_campaign', $campaign);
        $request->session()->put('utm_term', $term);
    }

    $headlines = HeadlineResolver::resolve($campaign, $term);

    return Inertia::render('Landing', [
        'headlines' => $headlines,
    ]);
}

Substitution Correctness Check

For QA, special mode: adding ?debug_headlines=1 to URL shows all available headline variants as table—convenient for testing before campaign launch.

Timeline

Headline dictionary + JS client-side substitution: 3-5 hours. Server-side substitution with session + Inertia: 4-6 hours. QA mode for checking all variants: 1-2 hours. Loading dictionary from CMS (editable list in admin): 1 day.