Software Development

How to Actually Make Your Website Fast

Alara Türkü

Alara Türkü

PlusClouds Author

Cloud & SaaS

Quick Summary

What Actually Made Our Site Fast: A Retrospective on Moving from Laravel to Next.js by the PlusClouds Engineering Team.

Everybody wants a fast website, but different websites how different journeys. This is our experience, our reasoning, our journey and our results. Here is what we did to make our website faster in 2026.

Join our community if you want to get more personlised advice for your website specifically.

Community

Further questions? Ask our team

There's a version of this post where we tell you we had a grand plan, executed it flawlessly, and shipped a perfectly optimized site. That's not what happened.

What actually happened is that we inherited a growing codebase, ran our first Lighthouse audit in a while, saw a score of 34 on mobile, and had a long, uncomfortable conversation about how we got there.

Why We Migrated from Laravel to Next.js

Migrating from Laravel to Next.js was not our first instinct, but it became the obvious choice. Our marketing site started as a Laravel application. Blade templates, server-rendered HTML, a sprinkling of jQuery. It was perfectly reasonable for the scale we were at. But as the product grew with multiple languages, pricing pages pulling live data, a blog with real traffic, complex auth flows; what was once "perfectly reasonable" became "quietly expensive to maintain."

The real issue wasn't performance. It was velocity. Every frontend change touched PHP. A copy edit meant a backend deployment. Adding a new page section meant data flowed through controllers, through view composers, down into Blade files. The people who should have owned the frontend (designers, marketers, production) couldn't touch it without an engineer in the loop.

We migrated to Next.js 15 with the App Router. Not because it was the easiest path, but because it gave us server-side rendering, static generation, incremental revalidation, and a proper component model, all in one framework. The decision was right. The execution had rough edges we're still smoothing out.

Our First Lighthouse Audit on Next.js Was Humbling

Our first Lighthouse audit on the new Next.js site didn't deliver the improvement we expected. When we stood up the new site and ran it for the first time, we expected an improvement over Laravel. We didn't get one, at least not immediately.

The score was worse in some categories. That was confusing until we understood why: migrating to a modern framework doesn't automatically make your site fast. It gives you better tools. Using those tools correctly is a separate problem.

The first thing we noticed that was slowing our site down was render-blocking resources. Not JavaScript, we'd been careful about that. The issue was subtler: external domains that we were fetching from had no browser hints. Our images lived on "$static.plusclouds.com$". Our API was "$apiv4.plusclouds.com$". Neither had a preconnect hint in the document head. So on every page load, the browser would parse HTML, encounter a request to an external domain, then begin the DNS resolution and TCP handshake from scratch.

Two lines in the root layout fixed it:

<link rel="preconnect" href="https://static.plusclouds.com" />
<link rel="preconnect" href="https://apiv4.plusclouds.com" />

It sounds almost too simple. But preconnect hints are one of those things where the cost of not having them compounds across every single page load, for every single visitor. The browser now starts establishing those connections before it even encounters the first image or API call. So, preconnect hints are one thing to look out for if you want to make your website faster.

How We Reduced JavaScript Bundle Size with Better Script Management

Reducing JavaScript bundle size with better script management was one of the quieter but most impactful wins in our migration. In the Laravel era, scripts had accumulated organically. Analytics here, a chat widget there, a third-party pixel added during a marketing campaign that never got removed. Each one was a synchronous or deferred script tag, and the order they were added reflected the order features were shipped, not any thoughtful consideration of what the page actually needed to render.

When we moved to Next.js, we carried some of those habits with us with better script management. We had components importing entire icon libraries when they used two icons. We had utility functions imported at the top of files that were only needed in one specific branch. We had client components marked 'use client' that had no interactivity at all; they just happened to be written that way when someone wasn't sure.

Each of these are is small elements. Collectively, they add up to a meaningfully larger JavaScript bundle. We went through the codebase methodically: every third-party script was evaluated for whether it needed to be in the < head > or could be deferred. Every large import was checked against actual usage.

Components that didn't need browser APIs were converted back to server components. The bundle size dropped, and with it, the time before the page became interactive.

The script bundle rule we settled on to make the site faster is this: nothing gets imported until it's needed, nothing runs in the browser that can run on the server, and nothing loads in the < head > unless it would block a visible render.

If you want more leads to refer to your fast website, check out our ai tool that automates the sales process.

LeadOcean

Start generating leads free

Next.js Image Optimization: WebP, CDN, and the Priority Prop

Next.js image optimization — including format conversion, CDN delivery, and the priority prop, was the most expensive line item on any page, and we got it wrong more than once. Everyone nods at image optimization and then goes back to uploading a 3.7MB JPEG directly from Figma export. We did exactly that. Several times.

The custom image loader we built routes all production traffic through imgproxy on our CDN. Every image is resized to the dimensions actually needed, served in the right format for the requesting browser, and cached at the edge. A mobile visitor on a 390px screen doesn't download a 1600px version of the same image anymore. The loader transparently handles all of this; the developer writes a normal < Image > tag and the CDN does the rest.

But format conversion is a separate problem from delivery. A JPEG served from a fast CDN is still a JPEG. Converting hero backgrounds and large marketing images to WebP cuts file sizes by 60 to 80 percent. We had a 1.3MB PNG on our homepage hero and didn't catch it until Lighthouse flagged it for the fourth consecutive audit. That image should weigh around 200KB in WebP. The CDN can't fix that; only converting the source file can.

The other thing we got wrong for longer than we should have: the priority prop on Next.js images. The framework lazy-loads images by default, which is the right behavior for images below the fold. But the Largest Contentful Paint element (almost always the hero image) should never be lazy-loaded. Adding priority tells Next.js to preload that image and stop treating it like optional content. It's one prop. The LCP improvement is immediate and measurable.

image

Next.js Rendering Strategy: Static Generation, ISR, and Dynamic Rendering

Choosing the right Next.js rendering strategy (static generation, ISR, or dynamic rendering) is one of the most important architectural decisions in the framework, and it has real performance consequences.

Server-rendered on every request, statically generated at build time, statically generated with periodic revalidation: these are different tools for different problems, and using the wrong one has real performance consequences.

Our product pages (server specs, pricing tiers, feature documentation) don't change by the minute. They now use generateStaticParams combined with ISR and a one-hour revalidation window. At build time, every locale combination gets pre-rendered to static HTML. A user hitting a product page gets a cached file served from the CDN edge in under 100ms. There's no server, no database query, no Node.js process waking up.

Pages that do need fresh data (the blog index, live pricing, anything user-specific) use short revalidation windows or dynamic rendering with caching at the component level. The result is that the overwhelming majority of our traffic is served from static HTML, and the server only does real work when the content has actually changed.

TTFB on most pages is now consistently under 100ms. On the old Laravel setup, it was rarely under 400ms. That's not the framework doing magic, it's a consequence of deciding deliberately, for each page, what rendering strategy makes sense.

SEO structured data, hreflang tags, and OpenGraph metadata are invisible to users but consequential for everything else, and we had almost none of them when we migrated. Optimizing for Google's generative AI features requires well-structured, crawlable content with proper schema markup.

We added FAQPage JSON-LD to product and feature pages. SoftwareApplication schema for our tools. BreadcrumbList for navigation hierarchy. Organization schema in the root layout. These are read by search crawlers and increasingly by the AI systems that decide whether to include your content in generated summaries and answer boxes.

The hreflang situation was messier. We support four locales and our alternate links were inconsistent at best, missing at worst. We built a shared buildAlternates() function that generates correct hreflang tags from a single path argument and runs on every page. It's the kind of thing that feels like housekeeping until you realize wrong or missing hreflang tags are actively hurting your international search performance.

OpenGraph metadata was similar, some pages had it, some didn't, the image dimensions were inconsistent. Centralizing it through Next.js's generateMetadata API and adding metadataBase to the root layout meant we stopped thinking about it per-page and started getting it right everywhere by default.

Results: Lighthouse Score Above 90, LCP Under 2.5s, TTFB Under 100ms

After the migration, our Lighthouse score on mobile is consistently above 90. CLS is under 0.05. LCP is under 2.5 seconds on most pages. TTFB is under 100ms for statically served content.

We're not done. There are still images that should be WebP but aren't, pages that could be statically generated but default to dynamic rendering, and structured data that could be more specific. The list is shorter than it was, but it never reaches zero.

The honest lesson from all of this is that performance isn't a project you finish, it's a set of defaults you establish. priority on every hero image before the PR merges. preconnect added the same day a new external domain is introduced. Structured data written alongside the page content, not added retroactively. Imports audited before they become bundle weight.

When those things are habits rather than checklists, you stop spending sprints recovering from accumulated decisions and start shipping fast by default.

Join our community if you want to get more personlised advice for your website specifically.

Community

Further questions? Ask our team

Check out our servers to unlock fast, intelligent infrastructure from one single panel. Our free features like automated backups and restoring, scheduled healthchecks and instant scaling help to make your website operate better. Feel free to check them out.

PlusClouds Cloud

Deploy your first server today

##fast website##faster site#migration from laravel to nextjs#laravel to nextjs#leaving laravel