How to improve website performance without sacrificing design
Back to notes

How to improve website performance without sacrificing design

A fast website doesn’t have to look simple, just as a visually rich website doesn’t have to be slow.

Performance and design are still often treated as if they sit on opposite sides of the same decision: the more photography, typography, animation and interaction we introduce, the more performance we are expected to sacrifice, while optimisation is imagined as a process of gradually removing those elements until the website becomes fast enough.

In practice, I rarely find the relationship that simple. A visually minimal website can perform poorly because it loads unnecessary JavaScript, oversized assets, multiple font files and third-party scripts, while a much richer experience can remain remarkably fast when those same resources are prioritised, delivered and rendered thoughtfully.

Recent data makes the importance of that distinction particularly clear. In 2026, Shopify analysed actively selling stores on its platform and found that every additional 100 milliseconds of Largest Contentful Paint (LCP) was associated with conversion being approximately 3.5% lower; stores with an LCP of 2.5 seconds reported roughly 30% lower conversion than those at 1.5 seconds. Shopify is careful to describe this as a relationship rather than proof that speed alone determines conversion, since price, product, traffic quality and many other factors influence whether someone ultimately buys.

For me, that is the more useful way to think about performance. It isn't a technical score that developers improve after the design is finished, but part of the experience itself, influencing how quickly someone can understand a page, interact with it and move through it without friction.

The goal, therefore, isn't to remove design until a website becomes fast. It is to understand where an experience is actually expensive, and then make better decisions about how it is designed and built.

01 — Start with what is actually expensive

When a website feels slow, visual elements tend to receive the blame first. The large hero photograph looks expensive, the animation feels unnecessary, the custom typeface could be replaced with a system font, and before long optimisation becomes a process of simplifying the visual direction rather than understanding the actual problem.

Before changing the design, I prefer to measure what is happening.

A relatively simple page might be downloading several megabytes of JavaScript because of one dependency, loading six different font files before they are needed, requesting an enormous image for a small mobile viewport, or waiting for third-party scripts that contribute almost nothing to the experience.

Third-party technology deserves particular attention because its cost is easy to overlook. Yottaa's 2025 Web Performance Index found that third-party applications accounted for approximately 44% of total page load time across the ecommerce sites in its dataset, while each poorly optimised third-party application was associated with an average 0.29% reduction in conversion.

This is why performance optimisation should begin with diagnosis rather than reduction. If a tracking script, chat widget or oversized dependency is responsible for the bottleneck, removing a carefully considered animation isn't going to solve the right problem.

02 — Keep strong imagery, but deliver it intelligently

Large photography can be central to a digital identity, particularly for fashion, architecture, luxury, hospitality and product-focused websites, so reducing every image until it becomes visually insignificant is rarely an attractive solution.

What matters much more is whether the browser receives the right image for the context in which it will actually be displayed.

A 3000-pixel photograph doesn't need to be downloaded when it occupies a few hundred pixels on a mobile screen, just as an image halfway down a long page usually doesn't need the same loading priority as the hero image that defines the first viewport. Responsive image sizing, appropriate compression, modern formats such as WebP and AVIF, and considered lazy loading can substantially reduce what the browser has to download without changing the composition at all.

One of the most interesting recent examples comes from Nuvemshop, an ecommerce platform supporting more than 180,000 stores. Between January 2025 and January 2026, the percentage of its stores with good LCP increased from 57% to 96%, while its overall Core Web Vitals pass rate increased from 48% to 72%. Within the same cohort of Brazilian stores, mobile visitors arriving through Google organic search showed an 8.9% increase in conversion rate and an 8.4% increase in cart engagement.

What makes this case particularly relevant is that the solution wasn't simply to compress every image more aggressively. Nuvemshop discovered that above-the-fold images were sometimes being lazy-loaded, important images lacked priority signals, and CSS transitions affected which element the browser recognised as the LCP candidate. The team changed how those assets were prioritised and rendered rather than compromising the visual flexibility of more than 180,000 individual storefronts.

That is exactly the kind of optimisation I find most interesting: the experience looks the same, but the browser has been given much better instructions about how to build it.

Practical check

Before measuring performance locally, I test a production build rather than the development server, because development mode includes additional tooling and overhead that can make the website appear significantly slower than it will be in production.

For a Next.js project, I usually create and run the production build first:

npm run build
npm run start

Once the production version is running, I open the Network panel in Chrome DevTools, enable Disable cache and reload the page. Sorting requests by size makes oversized assets surprisingly easy to spot, while network throttling provides a more realistic picture of how the experience behaves outside a fast development environment.

For individual images, Squoosh is useful for comparing formats and compression levels visually, while Next.js Image can handle responsive sizing, modern formats and image optimisation as part of the application.

The important part is to look at the result rather than chasing the smallest possible file size. Compression that introduces visible artefacts into skin, gradients, typography or product photography isn't good optimisation for a design-led website.

03 — Treat typography as part of performance

Typography contributes enormously to how a digital product feels, so replacing a distinctive typeface with a system font purely for performance reasons is rarely my first choice. The more useful question is whether the website is loading more typography than the design actually uses.

A type family can easily grow from Regular and Medium into Light, SemiBold, Bold, several italics and perhaps a second display family, after which the browser may be downloading a considerable amount of font data before the interface has fully settled.

I prefer to look at the design system and ask which weights genuinely create hierarchy, whether a variable font could replace several separate files, which fonts need to be available immediately, and whether less important styles can be loaded later.

Chrome DevTools makes this easy to inspect because the Network panel can be filtered specifically for fonts, showing exactly which files are requested and how large they are. It is a small audit, but it often reveals that a website is downloading typography that nobody realised was still being used.

The objective isn't to make typography less expressive; it is to make sure every file being delivered has a reason to be there.

04 — Animation isn't the enemy of performance

Animation becomes expensive mainly when it repeatedly forces the browser to recalculate layout or repaint large parts of the page. For most interface motion, I therefore prefer animating transform and opacity rather than properties such as width, height, top, left or margin, which can trigger additional layout work.

There is a practical reason this matters. On a typical 60 Hz display, the browser has roughly 16.7 milliseconds to produce each frame if an animation is going to maintain 60 frames per second, and that small window has to accommodate more than the animation itself: JavaScript, style calculations, layout, painting and compositing may all need time within the same frame. Once the work regularly exceeds that budget, frames can be missed and motion starts to appear less smooth.

Modern devices can make the requirement even more demanding. A 120 Hz display refreshes approximately every 8.3 milliseconds, leaving an even smaller frame window when the experience is expected to take advantage of the higher refresh rate. This is why an animation that appears perfectly smooth on one device isn't necessarily evidence that its implementation is efficient.

The same principle applies to scroll-based animation. Instead of attaching heavy calculations directly to every scroll event, I use tools designed to manage updates efficiently, such as GSAP ScrollTrigger, and avoid continuously reading and changing layout values inside the same animation cycle. For simpler effects, CSS transitions and animations are often enough and don't need an additional JavaScript library.

With Canvas and WebGL, I pay particular attention to effects that continue rendering when nothing is happening. A continuous render loop can keep consuming CPU and GPU resources even when the effect is outside the viewport or the browser tab is no longer visible, so I pause or reduce rendering when the scene isn't being viewed and avoid rendering at an unnecessarily high pixel density when the visual difference is negligible.

Interaction performance matters here as well. Google's current Core Web Vitals guidance considers an Interaction to Next Paint (INP) of 200 milliseconds or less to represent good responsiveness at the 75th percentile of page visits. An animation may look smooth once it has started, but if the main thread is occupied when someone clicks, taps or opens a menu, the interface can still feel slow.

I also treat mobile separately rather than assuming that an animation performing well on my development machine will behave equally well everywhere. Complex pointer effects can usually disappear on touch devices, while heavier WebGL or particle effects can use fewer elements, a lower pixel ratio or a simpler alternative on devices where the full effect would add considerable rendering cost.

When something still feels slow, I record the interaction in Chrome DevTools → Performance and look for long tasks, repeated layout calculations, excessive painting and dropped frames. This gives me a much better basis for optimisation than removing animation at random, because it shows whether the problem is the visual effect itself, the amount of work happening on the main thread, or simply the way the animation has been implemented.

05 — Decide what actually needs to load now

A user opening a page doesn't need every asset, component and interaction on the website before they can begin reading the first screen, yet it is surprisingly easy to build pages that behave as if they do.

Images outside the initial viewport can usually wait, heavy interactive functionality can often be loaded when it becomes relevant, and code belonging to another part of the experience doesn't necessarily need to be included in the initial JavaScript bundle.

This is where loading strategy becomes closely connected to design hierarchy. If the first screen communicates the brand, the purpose of the page and the next available action, those elements deserve priority; a carousel several sections below, an interactive configurator the user hasn't opened or a video that won't appear for another two scrolls usually does not.

The question I ask is less “How do we make the entire website load immediately?” and more “What does the user actually need immediately?”

Once that hierarchy is clear, techniques such as lazy loading, code splitting and dynamic imports become much easier to apply intelligently.

06 — Be deliberate about JavaScript

JavaScript makes many of the experiences I enjoy building possible, but it is also one of the easiest ways for a website to become unnecessarily expensive because every dependency added to a project ultimately has to be downloaded, parsed and executed somewhere.

This becomes particularly important in React and Next.js projects, where it can be tempting to make components client-side simply because the application already uses React. Static content doesn't necessarily need client-side JavaScript, a basic hover state rarely needs an animation library, and an entire utility package shouldn't be included because one small helper function was convenient.

When a bundle starts becoming suspiciously large, tools such as Next.js Bundle Analyzer can make the problem much more tangible by showing what is actually inside it. A dependency that looked harmless in package.json can suddenly occupy a significant portion of the bundle, or a complex component used on only one page may turn out to be loaded much more broadly than expected.

The goal isn't to avoid JavaScript, because that would remove much of what makes modern digital experiences interesting. It is to make sure the JavaScript we send to the browser is doing work that genuinely belongs there.

07 — Performance is also about perception

Technical performance and perceived performance aren't always identical.

Two pages can finish loading at approximately the same time while feeling completely different to use. One might establish its layout immediately, show meaningful content early and progressively introduce secondary elements, while another remains visually empty before suddenly displaying everything at once.

Stable layouts, sensible loading states, immediate interaction feedback and a clear hierarchy of what appears first can therefore improve the experience even when the total amount of data hasn't changed dramatically.

This is one reason I don't like treating a Lighthouse score as the final definition of performance. Metrics are extremely useful for finding problems, but the experience still needs to be used by a person.

A fast website should not merely finish loading quickly; it should feel ready when the user is ready to interact with it.

08 — Mobile is where performance decisions become visible

A website running smoothly on a powerful laptop connected to fast Wi-Fi tells us relatively little about how resilient the experience really is, because mobile introduces different processors, different network conditions, touch input and much less tolerance for interactions that hesitate.

This remains a widespread challenge even as devices and networks improve. According to the 2025 HTTP Archive Web Almanac, only 48% of mobile websites achieved good Core Web Vitals, compared with 56% on desktop. Mobile performance has improved considerably from 32% in 2021, but more than half of the sites measured still failed to deliver a good result across all three Core Web Vitals.

I don't think the answer is to give mobile users an aesthetically inferior version of the website. Instead, the experience should respond to the capabilities and interaction model of the device.

A complex pointer effect has little purpose on a touch screen anyway, an expensive background effect might have a lighter alternative, and an autoplaying visual doesn't necessarily need to begin consuming resources before it becomes relevant.

Responsive design, in that sense, should respond to more than viewport width. It should consider how the experience is actually being accessed and what is appropriate in that context.

09 — Measure before you compromise

Performance work becomes much less useful when decisions are based on assumptions.

An animation may look like the obvious problem while the real bottleneck is a third-party script; an image may appear enormous while already being efficiently delivered; a custom font may receive the blame while several hundred kilobytes of unused JavaScript are being executed on every page.

This is why my workflow usually starts and ends with measurement.

I begin with Lighthouse for a broad diagnostic view, then use PageSpeed Insights to look at Core Web Vitals and, where available, real-user Chrome data. From there, Chrome DevTools becomes more useful than a single score because the Network panel shows what is actually being downloaded, while the Performance panel helps identify expensive main-thread work, layout calculations and animation issues.

For Core Web Vitals, the current thresholds remain useful reference points: LCP should be no more than 2.5 seconds, INP no more than 200 milliseconds and CLS no more than 0.1 for a page to be considered “good” at the 75th percentile of visits.

I don't treat those numbers as the design brief, however. They are indicators that help identify where the experience deserves attention.

A practical workflow I actually use

When a website feels slower than it should, I prefer to work through the problem in a predictable order rather than making several unrelated optimisations and hoping the score improves.

I start with PageSpeed Insights and Lighthouse to establish which metrics are struggling, then move to Chrome DevTools and inspect the Network panel to see which resources are actually expensive. If images dominate the transfer size, I check their dimensions, formats, compression and loading priority; if JavaScript is the larger problem, I inspect the bundle and look for unnecessary client-side code or dependencies; if interaction feels slow despite reasonable loading metrics, I record the experience in the Performance panel and look at what is occupying the main thread.

I then repeat the test under throttled network and CPU conditions and, importantly, on a real phone, because a website that performs beautifully on the machine used to build it can hide problems that become immediately obvious elsewhere.

Only after identifying the actual bottleneck do I change the implementation or, when genuinely necessary, reconsider part of the design.

The tools I reach for most often

PageSpeed Insights gives me a quick view of Core Web Vitals and, when enough field data is available, lets me compare a controlled performance test with how the website performs for real Chrome users.

Chrome Lighthouse is useful for an initial technical audit covering performance, accessibility and related implementation issues, although I use its score as a diagnostic starting point rather than a target in itself.

Chrome Network shows exactly what the page downloads, how large individual resources are and when they arrive, making it particularly useful for finding oversized images, fonts, JavaScript bundles and third-party requests.

Chrome Performance is where I look when the problem is interaction, animation or main-thread work rather than initial loading, because it allows me to inspect rendering, layout calculations, long tasks and individual frames.

Next.js Bundle Analyzer helps reveal which dependencies and modules are contributing to the JavaScript bundle, which is especially useful when a project has grown and the source of additional client-side weight is no longer obvious.

Vercel Speed Insights adds another layer that local testing cannot provide: real-user performance monitoring after the website has been deployed. I use it to see how Core Web Vitals behave across actual visits, devices and pages, rather than assuming that a good Lighthouse result on my own machine represents everyone else's experience.

Vercel Web Analytics gives that performance data useful context by showing how people actually move through the website, which pages receive traffic and where engagement is happening. I don't use analytics as a performance diagnostic tool itself, but it helps me decide where performance improvements are likely to matter most.

None of these tools should be treated as an automatic list of instructions. They provide evidence, while the developer still has to decide which changes make sense for the experience.

Performance doesn't require sacrificing visual identity

The most interesting performance work, to me, is the kind a user never notices.

The photography can remain large and expressive when the browser receives an appropriately sized and compressed version; distinctive typography can stay when unnecessary weights are removed and loading is handled carefully; motion can remain part of the interface when it is purposeful and implemented efficiently; even more technically ambitious experiences involving Canvas or WebGL can have a place when their cost is understood, their loading is intentional and the device running them is taken into account.

What should disappear is not the character of the website, but the unnecessary work happening behind it.

The Nuvemshop example is particularly convincing because the company improved good LCP from 57% to 96% without removing the flexibility that allowed merchants to build visually different storefronts, and those technical improvements coincided with measurable gains in mobile conversion and cart engagement.

That, for me, is the more useful definition of performance optimisation: not making the experience smaller, but making everything the experience asks the browser to do more intentional.

A website should not have to choose between being memorable and being fast; when design and development are considered together from the beginning, performance becomes part of the quality of the design itself.