Multi-Landing Page with Dynamic Content

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

Multilanding Development with Dynamic Content

A multilanding is a landing page that adapts content in real-time based on traffic source. One URL, different headlines, offers, images, phone numbers — depending on UTM parameters, user city, ad platform, keyword, or other signals. Used to increase relevance of ad campaigns without creating dozens of separate pages.

Why it matters

A user who clicked "iPhone 14 repair in New York" expects to see exactly that on the landing. If they land on a generic "Smartphone repair" page — conversion drops, Quality Score declines, click cost rises.

Multilanding solves scaling: instead of 50 pages for 50 keywords — one page with templating.

Architecture

Dynamic content can be substituted at three levels:

Client-side (JS after load) — simplest, but worst for SEO and CLS. User sees content "blinking" during substitution.

Edge (Middleware before render) — page renders on edge node with content already substituted. No CLS, SEO correctly indexes default version. Implemented via Vercel Edge Middleware, Cloudflare Workers, Netlify Edge Functions.

Server-side (SSR) — request processed on server, content substituted before HTML sent. Similar to Edge but runs on dedicated server, not distributed network.

Optimal choice — Edge Middleware for Vercel/Cloudflare projects or SSR for self-hosted.

Implementation with Next.js + Vercel Edge Middleware

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const { searchParams } = request.nextUrl;

  const utmTerm = searchParams.get('utm_term') ?? '';
  const utmSource = searchParams.get('utm_source') ?? '';
  const city = request.geo?.city ?? '';

  // Pass data in headers — read in component
  const response = NextResponse.next();
  response.headers.set('x-utm-term', utmTerm);
  response.headers.set('x-utm-source', utmSource);
  response.headers.set('x-visitor-city', city);

  return response;
}

export const config = {
  matcher: ['/landing/:path*'],
};
// app/landing/page.tsx
import { headers } from 'next/headers';
import { getContent } from '@/lib/content-engine';

export default function LandingPage() {
  const headersList = headers();
  const utmTerm = headersList.get('x-utm-term') ?? '';
  const city = headersList.get('x-visitor-city') ?? 'your city';

  const content = getContent(utmTerm);

  return (
    <main>
      <h1>{content.headline.replace('{city}', city)}</h1>
      <p>{content.subheadline}</p>
      <ContactForm phone={content.phone} />
    </main>
  );
}

Content engine: substitution matrix

Content substitution logic — separate module. Not hardcoded if/else, but configurable rules:

// lib/content-engine.ts

interface ContentVariant {
  headline: string;
  subheadline: string;
  cta: string;
  phone: string;
  image: string;
}

const defaultContent: ContentVariant = {
  headline: 'Smartphone repair in {city}',
  subheadline: 'Free diagnosis. 6-month warranty.',
  cta: 'Book repair',
  phone: '+1-555-000-0000',
  image: '/hero-default.webp',
};

const variants: Record<string, Partial<ContentVariant>> = {
  'repair iphone': {
    headline: 'iPhone repair in {city} — 1 hour',
    subheadline: 'Original parts. Repair while you wait.',
    image: '/hero-iphone.webp',
  },
  'repair samsung': {
    headline: 'Samsung repair in {city}',
    subheadline: 'Any Galaxy model. 12-month warranty.',
    image: '/hero-samsung.webp',
  },
  'screen replacement': {
    headline: 'Screen replacement in 30 minutes',
    cta: 'Get replacement cost',
  },
};

export function getContent(utmTerm: string): ContentVariant {
  const key = utmTerm.toLowerCase();
  const variant = Object.entries(variants).find(([k]) => key.includes(k));
  return { ...defaultContent, ...(variant?.[1] ?? {}) };
}

Geolocation and phone number substitution

For multi-location businesses — local phone number substitution is critical. Calling "local office" converts better:

const phoneByCity: Record<string, string> = {
  'New York': '+1 (212) 000-0000',
  'Los Angeles': '+1 (310) 000-0000',
  'Chicago': '+1 (312) 000-0000',
};

// In middleware — from request.geo (Vercel only with Geolocation)
const city = request.geo?.city ?? '';
const phone = phoneByCity[city] ?? defaultPhone;

Alternative for self-hosting — MaxMind GeoIP2 database or services like ipapi.co/ipgeolocation.io via API.

CallTracking

For each traffic source (Google Ads, Yandex.Direct, organic, social) — different phone via CallTracking service (CoMagic, Callibri, Ringostat). Number shown via JavaScript after page load:

// Dynamic CallTracking number replacement
window.ct_replace = {
  default: '+1-555-000-0000',
  pools: {
    'google': '+1-555-111-1111',
    'yandex': '+1-555-222-2222',
  }
};

Usually CallTracking service provides ready script with similar logic.

CMS for managing variants

Content variant matrix quickly grows. For marketer who doesn't edit code, need management interface:

UTM Term Headline Subheadline Image Phone
repair iphone ... ... hero-iphone.webp +1-555...
screen replacement ... ... hero-screen.webp +1-555...

Implemented as simple CRUD table in admin panel (Filament, Directus, even Google Sheets via API). Early on often enough JSON config in repo with deploy on change.

A/B testing variants

Multilanding + A/B test — combination for maximum optimization. On Edge Middleware can implement random split:

// Random 50/50 split
const variant = Math.random() < 0.5 ? 'A' : 'B';
response.cookies.set('ab_variant', variant, { maxAge: 60 * 60 * 24 * 7 });
response.headers.set('x-ab-variant', variant);

In page component read x-ab-variant and render appropriate version. Test results — in GA4 via custom dimensions.

SEO and multilanding

Dynamic content on one URL creates SEO risks. Googlebot sees default version. If multilanding — ad story without organic traffic, SEO doesn't matter. If page should rank — need separate URLs for each variant with canonical and hreflang.

Rule: multilanding for paid traffic, separate optimized pages for organic.

Typical timeline

Basic multilanding (UTM → headline/CTA) on static or Next.js — 5–7 business days. With geolocation, CallTracking, CMS for variants — 10–14 days. Full system with A/B testing, analytics, multilingual, 50+ variants — 3–4 weeks.