A Vue 3 SPA's marketing landing page downloads the whole app bundle and paints late; how would you cut its initial load?
answer
- what does the page need
- HTML first, not JS first
- SSG for static content
- then split, flags, registration
basics
~20 sServe the landing page as pre-rendered HTML — ideally SSG, shipped separately from the SPA — then shrink what JavaScript remains: lazy routes and async components, local instead of global registration, precompiled templates and feature flags.
solid answer
~40 sFirst measure what the page actually loads. Then fix the **architecture** before micro-optimizing: a marketing page's content is usually the same for everyone, so the Vue docs recommend shipping such pages **separately as static HTML with minimal JavaScript, using SSG**; SSR is the option when content varies per request. Either way the user sees content before the app boots, though hydration still needs the page's component code. Then shrink the JavaScript: make the rest of the app's **routes lazy** so the landing page does not pull their views; turn below-the-fold widgets into **async components**; replace **global plugin registration** of UI libraries with local imports so unused components are tree-shaken; make sure templates are **precompiled**, not compiled in the browser; set `__VUE_OPTIONS_API__` to `false` if nothing uses the Options API. Re-measure after each step.
code
vue · 16 lines<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
// Local import instead of a globally installed UI library
import BaseButton from './ui/BaseButton.vue'
// Below the fold: its chunk is fetched only when rendered
const Testimonials = defineAsyncComponent(() => import('./Testimonials.vue'))
</script>
<template>
<section class="hero">
<h1>Ship faster</h1>
<BaseButton>Start free trial</BaseButton>
</section>
<Testimonials />
</template>go deeper
Recall that lazy routes and async components keep code out of the first download, and that pre-rendered HTML shows content sooner.
Explain SSG versus SSR for first paint, and which Vue-specific bundle levers apply: local registration, precompiled templates, feature flags.
Lead with architecture, order the bundle work by measured impact, and verify each change without introducing request waterfalls.
Decide where the boundary between static marketing pages and the SPA belongs, and how bundle budgets are enforced for pages that must stay light.
## Start with what the page needs A marketing landing page has a simple job: show headline, copy, images and a call to action as fast as possible, with a little interactivity — a menu, a signup form. When it is just another route of a single-page app, the browser typically downloads the app's entry bundle, runs it, creates the Vue app, fetches data, and only then renders anything. Everything the app needs for its logged-in screens stands between the visitor and the first paint. Before changing anything, record what the page downloads and how long until content appears. That gives a baseline and tells you whether the problem is bytes, requests, or render work. ## Step 1 — fix the architecture The Vue docs are direct about this: if page load matters, avoid shipping the page as a purely client-side SPA; the server should send HTML containing the content. | Option | When it fits | What the visitor gets | |---|---|---| | **SSG** (pre-rendering at build time) | Content is the same for everyone and changes on deploy | Static HTML immediately, minimal JavaScript | | **SSR** (render per request) | Content varies per request or user | HTML immediately; hydration still downloads the page's components | | Separate static pages | Marketing pages next to an SPA | The SPA's bundle is not loaded at all on those pages | | Pure client-side SPA | Logged-in app screens | Blank or skeleton until JavaScript runs | For a landing, about and pricing page, the docs' recommendation is to **ship them separately as static HTML with minimal JS, using SSG**, and keep the SPA for the application itself. That single change usually dwarfs every other lever. ## Step 2 — shrink the JavaScript that remains Whether the page stays in the SPA or becomes pre-rendered with hydration, the JavaScript it loads should be only what it needs: 1. **Lazy routes.** Every other route's view becomes a dynamic import, so the landing page's chunk does not contain the dashboard. 2. **Async components** for below-the-fold or on-demand pieces: a video player, a testimonials carousel, a pricing calculator opened on click. 3. **Local registration.** A UI library installed with a global plugin keeps all its components in the bundle; importing the handful the page uses lets the rest be removed. 4. **Precompiled templates.** If the app compiles templates in the browser, it ships the compiler (about 14 kB min+gzip per the Vue docs) and pays compile time at startup. 5. **Compile-time flags.** With a Composition-only codebase and dependencies, `__VUE_OPTIONS_API__: false` removes Options API support; keep `__VUE_PROD_DEVTOOLS__` at its default `false`. 6. **Heavy dependencies.** Check what the entry chunk imports at module level — a date or charting library imported in `main.ts` for one screen is paid for on every page. ## Step 3 — verify - Re-measure time to content and the size of the page's JavaScript after each change, under the same conditions. - Check the build output to confirm heavy modules moved out of the landing page's chunk. - Watch for new waterfalls: an async component above the fold can make first paint later, not earlier. ## A worked order of operations For a typical case — a landing page living inside an SPA that has grown a large dashboard — a sensible sequence is: 1. Measure time to content and the landing page's JavaScript size. 2. Move the landing, about and pricing pages to pre-rendered static output served separately, keeping only small islands of interactivity. 3. If they must stay inside the SPA, make every other route lazy and check that the landing chunk no longer contains dashboard code. 4. Replace global UI-library registration with local imports and move module-level heavy imports into the components that use them. 5. Defer below-the-fold widgets with async components. 6. Configure compile-time flags and confirm templates are precompiled. 7. Re-measure, and keep the numbers as the page's budget. ## Trade-offs to name - SSR adds a server to run and constrains code that touches browser-only APIs; SSG adds a rebuild on every content change. - Splitting too finely multiplies requests. - Disabling Options API support can silently break dependencies. ## What the interviewer is listening for - Architecture first: HTML-first delivery for content pages, then bundle work. - Vue-specific levers named precisely: async components, lazy routes, local registration, precompiled templates, compile-time flags. - Measurement before and after, rather than a list of tricks.
- Why might SSR alone not make the Vue 3 landing page interactive sooner?SSR sends HTML, so content appears early, but the page becomes interactive only after the client downloads the components and hydrates them. If the bundle is still the whole app, users see content they cannot use yet. SSR helps time to content; bundle work still decides time to interactivity.
- When would you choose SSR over SSG for a Vue 3 marketing page?When the HTML must differ per request — personalised content, prices by region, frequently changing data that cannot wait for a rebuild. If the content is the same for every visitor and changes only on deploy, SSG gives the same time-to-content benefit with simpler, cheaper hosting.
saying these in an interview costs you the question
- Jumps to minification tweaks before questioning the SPA architecture.
- Believes SSR removes the need to download the page's JavaScript.
- Makes the hero section an async component to shrink the bundle.
- Keeps a UI library globally registered and expects unused parts removed.
- Declares victory without re-measuring after each change.