Framework Conversion Fixes

How to Improve Conversion on a Vue 3 Landing Page

TL;DR

On a Vue 3 landing page, the structural wins are: keep the primary CTA above the fold as a plain link, lazy-load heavy below-the-fold sections with defineAsyncComponent so they don't block first paint, and reserve their space to avoid layout shift. Each is a few lines of the Composition API.

The biggest conversion wins on a Vue 3 landing page are structural. Keep the primary CTA above the fold as a plain link, lazy-load heavy below-the-fold sections with defineAsyncComponent so they don't block first paint, and reserve their space so the page never shifts. These move the number far more than restyling the button.

Keep the CTA above the fold and JS-free

The primary action should be a real anchor or <router-link> in the first viewport, not buried under a tall hero or inside a component that hydrates late. A link works immediately; keep it simple:

<!-- HeroSection.vue -->
<template>
  <section class="hero">
    <h1>Ship a conversion fix every week</h1>
    <p>One high-impact fix, opened as a Pull Request.</p>
    <a class="cta" href="/agent/register">Start free trial</a>
  </section>
</template>

Lazy-load below-the-fold sections with defineAsyncComponent

A long landing page often ships a heavy testimonials carousel or feature grid that the visitor may convert before ever seeing. defineAsyncComponent is the Vue-specific tool: it loads a component's code only when it's first rendered.

<!-- App.vue -->
<script setup>
import { defineAsyncComponent } from 'vue'

// Code for these loads on demand, not on initial page load
const Testimonials = defineAsyncComponent(() => import('./Testimonials.vue'))
const FeatureGrid = defineAsyncComponent(() => import('./FeatureGrid.vue'))
</script>

<template>
  <HeroSection />

  <!-- Reserve the height so arrival doesn't shift layout -->
  <div style="min-height: 480px">
    <Suspense>
      <FeatureGrid />
      <template #fallback><div class="skeleton" /></template>
    </Suspense>
  </div>

  <div style="min-height: 600px">
    <Suspense>
      <Testimonials />
      <template #fallback><div class="skeleton" /></template>
    </Suspense>
  </div>
</template>

Two things working together:

  1. defineAsyncComponent + dynamic import() splits each heavy section into its own chunk, so the initial bundle is smaller and the hero paints sooner.
  2. The min-height wrappers reserve space, so when an async section resolves, the content below it doesn't jump — protecting your Cumulative Layout Shift.

Vue's built-in <Suspense> shows the fallback skeleton while the async component loads.

Steps to apply it

  1. Put the hero, headline, and a plain-anchor CTA in the first viewport.
  2. Identify heavy below-the-fold sections and convert them to defineAsyncComponent with a dynamic import.
  3. Wrap each in <Suspense> with a skeleton fallback.
  4. Set a min-height on the wrapper matching the loaded section's height.
  5. Repeat the CTA once near the bottom for visitors who read everything.

Confirm the hero is doing the work

If most conversions come from the top CTA, your structure is right. Capture cta_clicked with a location property and compare:

SELECT
  properties.location AS location,
  countDistinct(person_id) AS clickers
FROM events
WHERE event = 'cta_clicked'
  AND timestamp > now() - INTERVAL 30 DAY
GROUP BY location
ORDER BY clickers DESC

Illustrative sample output:

location clickers
hero 540
footer 120

If the hero dominates, the above-the-fold CTA plus a fast first paint is carrying conversion — exactly what the lazy-loading buys you. If you'd like the slowest, weakest section found and fixed as a Pull Request, that's what Velyr does.

Frequently asked questions

How do I lazy-load a component in Vue 3?

Use defineAsyncComponent, which returns a component that only loads its code when it's first rendered. Pair it with Vue's built-in Suspense or a v-if tied to a visibility check so heavy below-the-fold sections don't download until they're needed.

Where should the CTA go on a Vue 3 landing page?

Above the fold, as a plain anchor or router-link, visible without scrolling on mobile and desktop. Keep it out of any lazily-hydrated section so it's available immediately, and repeat it once lower down for visitors who read the whole page.

How do I stop layout shift from a lazy-loaded Vue section?

Give the placeholder the same height as the loaded section using a min-height, so when the async component arrives the content below it doesn't jump. Layout shift from late-loading components is a common, avoidable Cumulative Layout Shift problem.

Velyr is an AI growth agent that ships one weekly conversion fix as a GitHub Pull Request — you approve it over Telegram, and it rolls itself back if the numbers drop.

Start the Growth Agent