[Blog](https://console.f1b10.testing.getox.in/blog) [Engineering](https://console.f1b10.testing.getox.in/blog#c-engineering) # Case Study: Raising a Vue App's Lighthouse Score to 93 A Vue and Vite case study: how route splitting, lazy components, deferred analytics and leaner fonts raised a Lighthouse performance score from 60 to 93. Saurav Sharma Mar 10, 2026 6 min read On this page - [The Problem](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-the-problem) - [Strategy](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-strategy) - [What I Changed](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-what-i-changed) - [Benchmarks](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-benchmarks) - [Why This Worked](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-why-this-worked) - [Key takeaways](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#key-takeaways) - [Frequently asked questions](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#faq) - [Final Summary](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-final-summary) - [Before you go:](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-before-you-go) I raised our landing page's Lighthouse performance score from about **60** to **93** by shrinking what the first request loads, not by removing features. The changes were route-level code splitting, lazy-loaded heavy components, analytics started after the page is idle, and fewer fonts up front. The app is a Vue single-page app built with Vite. Our landing page was visually strong, but the frontend was shipping too much too early. **In this guide:** - [The problem: a 1.8 MB main bundle](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-the-problem) - [What I changed, in five steps](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-what-i-changed) - [Benchmarks before and after](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-benchmarks) - [Why this worked](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-why-this-worked) - [Frequently asked questions](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#faq) The biggest symptoms were: - A Google performance score around **60** on the landing page - A very large production bundle - Heavy CSS and font payloads - Too much non-critical work happening during initial load *This was the warning I was getting from Vite while running the build command.* Shell ```bash vite v6.4.1 building for production... ✓ 2527 modules transformed. dist/index.html (!) Some chunks are larger than 500 kB after minification. Consider: - Using dynamic import() to code-split the application - Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit. ✓ built in 9.64s ``` Raising `build.chunkSizeWarningLimit` only hides that warning. The fix is to load less on the first page, which is what the rest of this post does. The option is described in Vite's [build options reference](https://vite.dev/config/build-options). This write-up focuses only on the **frontend** side. API performance was intentionally left out. --- ## The Problem From the original production build: - Main JS bundle: **1,841.83 kB** minified - Main CSS bundle: **269.42 kB** - Gzipped JS: **590.54 kB** - Gzipped CSS: **61.27 kB** That meant the browser had to parse, download, and evaluate far more than the landing page actually needed. --- ## Strategy I kept the plan simple: 1. Reduce the **critical path** 2. Load only what the current page needs 3. Push non-essential work **after first paint** 4. Keep regressions low by changing loading strategy first, not rewriting features --- ## What I Changed ### 1. Route-level code splitting The landing page should not pay the cost of planner screens, task editing flows, integrations, and dashboard code. I changed the app so major routes load on demand instead of being pulled into the first page load. JavaScript ```js const routes = [ { path: "/", component: () => import("...HomePage...") }, { path: "/app", component: () => import("...WorkspaceView...") }, { path: "/settings", component: () => import("...SettingsView...") }, ]; ``` Vue Router turns each dynamic `import()` into its own chunk, fetched the first time the route is visited ([lazy loading routes](https://router.vuejs.org/guide/advanced/lazy-loading) in the Vue Router docs). ### 2. Lazy-loading expensive UI inside authenticated screens Even inside the app, some heavy UI only appears after user interaction. Examples: - task edit modal - integration panels - filter sidebar - device-specific overlays So instead of bundling them into the initial workspace render, I loaded them only when needed. JavaScript ```js const HeavyModal = defineAsyncComponent(() => import("...TaskEditor...")); ``` You can read more about `defineAsyncComponent` in the [official Vue docs on async components](https://vuejs.org/guide/components/async). ### 3. Deferring analytics and monitoring Analytics, session tracking, and error monitoring are useful, but they do not need to compete with first paint. I moved them behind a delayed, idle-time startup. JavaScript ```js runAfterLoadAndIdle(() => { setTimeout(() => { initAnalytics(); initMonitoring(); loadThirdPartyScript(); }, 4000); }); ``` This was one of the highest-leverage changes for Lighthouse. ### 4. Font loading discipline The previous build was shipping a wide set of font families and language subsets up front. I changed that strategy to: - load only base fonts first - load landing-page accent fonts only on the landing page - load theme-specific fonts only when that theme is active - use narrower subsets instead of pulling everything by default JavaScript ```js loadBaseFonts(); loadLandingFonts(); loadThemeFonts(currentTheme); ``` ### 5. Reducing work on the landing page itself The landing page also had runtime work that looked nice but did not need to happen immediately. I optimized that by: - delaying visual effects until idle time - keeping below-the-fold sections out of the initial rendering cost - adding image dimensions to reduce layout work - preserving design while trimming early execution --- ## Benchmarks ### Build Footprint | Metric | Before | After | | --- | --- | --- | | Main landing-path JS | 1,841.83 kB | about **237 kB** critical JS | | Main landing-path CSS | 269.42 kB | about **24 kB** critical CSS | ### Lighthouse Snapshot | Metric | Before | After | | --- | --- | --- | | Landing page performance score | ~60 | 93 | ### Notes: - The new JS number reflects the critical landing path, not the entire app footprint. - Large authenticated features still exist, but they are no longer loaded up front on the landing page. - The score is from a local Lighthouse run. Lighthouse scores move between runs and devices, so compare runs made on the same machine and settings ([how Lighthouse scores performance](https://developer.chrome.com/docs/lighthouse/performance/performance-scoring)). --- ## Why This Worked The biggest lesson was straightforward: > I did not need less product. I needed less product in the first request. Most performance issues were not caused by one huge mistake. They came from many reasonable frontend decisions stacking together: - eager route imports - global loading of rare UI - early analytics boot - broad font loading - decorative runtime work too early Once I treated the landing page like a focused entry experience instead of the whole app, performance improved quickly. --- ## Key takeaways - Start with the **critical path**, not micro-optimizations - Route splitting gives outsized wins when a landing page and app shell live together - Third-party scripts should earn their place in the first few seconds - Font strategy matters more than many teams think - Good performance work is often about **loading order**, not feature removal ## Frequently asked questions ### What is a good Lighthouse performance score? Lighthouse rates a performance score of 90 to 100 as good, 50 to 89 as needing improvement, and below 50 as poor. This landing page started around 60, in the middle band, and reached 93 after the changes above. Scores vary between runs, so compare runs made on the same machine. ### How do I fix the Vite warning that chunks are larger than 500 kB? Split the code instead of raising the limit. Load each route with a dynamic import, load heavy components with defineAsyncComponent, and keep rarely used features out of the first page. Raising build.chunkSizeWarningLimit only hides the warning, and manualChunks helps caching more than it shrinks the first load. ### Should I lazy-load every Vue component? No. Lazy-load what the first screen does not need: other routes, and heavy UI that only appears after a click, like editors, modals and integration panels. Components visible on first paint should stay in the main chunk, or the page waits on extra requests before it can show anything useful. ### Did the speedup mean removing features? No. Every feature stayed in the product. The work changed when code loads, not what the app does: the landing page stopped downloading planner screens, task editors, integrations and analytics it never used. In this case study, loading order mattered more than cutting features, which kept the risk of regressions low. ## Final Summary I improved page speed by shrinking the initial frontend payload, deferring non-essential work, and keeping heavy product features out of the landing-page critical path. The result was a much lighter first load, better rendering behavior, and a local Lighthouse score that moved from the low-performance range into the **90+** range without redesigning the product. If your Vue or Vite app is a static single-page app, the [guide to deploying a static site or Vite SPA with ox](https://console.f1b10.testing.getox.in/docs/guides/static-site) shows how it is served by Caddy on your own server, with no app process. For a Vue frontend with a Django API, see the [Django and Vue.js VPS deployment guide](https://console.f1b10.testing.getox.in/blog/deployment-guide-for-django-vuejs-project-on-vps), and for why I build tools this way, [the story behind ox](https://console.f1b10.testing.getox.in/story). ## ***Before you go:*** If you're someone struggling with managing time, then check out this lightweight but powerful web app [Lazy Planner](https://lazyplanner.app). I made this for myself first because I couldn't find one that better suits my needs, but I believe if it's useful for me, it could be useful for more people too. *Updated Oct 8, 2026: added a short summary, a FAQ, notes on the Vite warning and Lighthouse scoring, and links to the official docs.* First published on [blog.sorv.dev](https://blog.sorv.dev/case-study-how-i-improved-frontend-page-speed). New to running apps on your own server? [How ox self-hosts your apps](https://console.f1b10.testing.getox.in/self-hosting) shows what ox sets up on your server. On this page - [The Problem](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-the-problem) - [Strategy](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-strategy) - [What I Changed](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-what-i-changed) - [1. Route-level code splitting](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-1-route-level-code-splitting) - [2. Lazy-loading expensive UI inside authenticated screens](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-2-lazy-loading-expensive-ui-inside-authenticated-screens) - [3. Deferring analytics and monitoring](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-3-deferring-analytics-and-monitoring) - [4. Font loading discipline](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-4-font-loading-discipline) - [5. Reducing work on the landing page itself](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-5-reducing-work-on-the-landing-page-itself) - [Benchmarks](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-benchmarks) - [Build Footprint](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-build-footprint) - [Lighthouse Snapshot](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-lighthouse-snapshot) - [Notes:](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-notes) - [Why This Worked](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-why-this-worked) - [Key takeaways](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#key-takeaways) - [Frequently asked questions](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#faq) - [What is a good Lighthouse performance score?](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#faq-good-score) - [How do I fix the Vite warning that chunks are larger than 500 kB?](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#faq-vite-warning) - [Should I lazy-load every Vue component?](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#faq-lazy-everything) - [Did the speedup mean removing features?](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#faq-features) - [Final Summary](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-final-summary) - [Before you go:](https://console.f1b10.testing.getox.in/blog/case-study-how-i-improved-frontend-page-speed#heading-before-you-go) Written by Saurav Sharma Builds ox. Writes about running apps on servers you own, without Docker and without a platform bill. [Older post Deploy Django and Vue.js on a VPS: Nginx, Gunicorn, Celery](https://console.f1b10.testing.getox.in/blog/deployment-guide-for-django-vuejs-project-on-vps) [Newer post Agentic Loops and Swarms Are the New Moat for Dev Tools](https://console.f1b10.testing.getox.in/blog/agentic-loops-swarm-is-the-new-moat) ## Keep reading - Engineering [Zero-downtime deploys without Docker: systemd and Caddy](https://console.f1b10.testing.getox.in/blog/zero-downtime-deploys-systemd-caddy) Zero-downtime deployment on a VPS without Docker: blue-green with two systemd units, a health check and a Caddy upstream file. A copyable script inside. Saurav Sharma Oct 8, 2026 8 min read - Guides [How to Self-Host Web Apps on Your Own VPS (2026 Guide)](https://console.f1b10.testing.getox.in/blog/self-host-web-apps-on-your-own-vps) What you need to self-host a web app on a VPS: server, domain, HTTPS, database, backups. Four ways to set it up, their trade-offs, and how ox does it. Saurav Sharma Oct 15, 2026 8 min read - Product [A Coolify Alternative Without Docker, on Your Own VPS](https://console.f1b10.testing.getox.in/blog/coolify-alternative-without-docker) Want Coolify's dashboard without Docker? How ox runs apps as plain systemd services on your VPS, a fair comparison, and when Coolify is the better pick. Saurav Sharma Oct 15, 2026 7 min read ## Deploy to a server you own ox sets up systemd, Caddy and PostgreSQL on your Ubuntu server and deploys on every push. Free for your first project. [Sign up with GitHub](https://console.f1b10.testing.getox.in/login/github) [Read the docs](https://console.f1b10.testing.getox.in/docs)