Aquarious Technology
How We Built
Technical Case StudyMay 9, 2026

How We Built a 97/100 PageSpeed Website Using Next.js

Aditi Basu

Aditi Basu

Performance Engineering Contributor

Our website at theaquarious.com scores 97/100 on Google PageSpeed Insights (desktop). In an industry where most IT company websites score between 55-75, this isn't just a vanity metric - it's a competitive advantage that improves our SEO rankings, user engagement, and conversion rates.

This is the technical story of how we built it, what decisions we made, and what we learned.

The Challenge

When we redesigned the Aquarious Technology website, we had specific goals:

1

97+ PageSpeed score - on desktop, 85+ on mobile

2

Full SEO architecture - SSR, JSON-LD schema on every page, optimized metadata

3

Rich visual design - animations, gradients, partner showcases with images

4

Image-heavy portfolio - 30+ case studies with high-quality screenshots

5

No Vercel image optimization costs - we needed unlimited images without the Vercel quota

The last point is critical. Vercel's image optimization API has usage limits that, once exceeded, return 402 errors. With 30+ portfolio pages, each containing multiple screenshots, we hit the ceiling quickly.

Architecture Decisions

01

Decision 1: Next.js App Router (Not Pages Router)

We chose the App Router (introduced in Next.js 13, stable in 14+) over the Pages Router because:

  • React Server Components - Render non-interactive UI on the server, sending zero JavaScript to the client. Our homepage hero section, about section, and footer are all Server Components.
  • Streaming - Components render and stream to the client progressively, improving perceived load time.
  • Built-in metadata API - Type-safe, centralized metadata management without third-party libraries.
  • Parallel routes and intercepting routes - Future-proofing for complex UI patterns.

The trade-off: App Router has a steeper learning curve and some patterns differ from the Pages Router. We accepted this for the long-term architectural benefits.

02

Decision 2: Custom Cloudinary Image Loader

Instead of using Vercel's built-in image optimization (which has quota limits), we built a custom Cloudinary loader:

// lib/cloudinary-loader.ts
export default function cloudinaryLoader({
  src,
  width,
  quality,
}: {
  src: string;
  width: number;
  quality?: number;
}) {
  const cloudName = process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME;
  const params = [
    'f_auto',       // Automatic format (WebP/AVIF)
    'c_limit',      // Constrain without cropping
    `w_${width}`, // Responsive width
    `q_${quality || 'auto'}`, // Adaptive quality
  ];

  // For external URLs, use Cloudinary's fetch mode
  if (src.startsWith('http')) {
    return `https://res.cloudinary.com/${cloudName}/image/fetch/${params.join(',')}/${src}`;
  }

  return `https://res.cloudinary.com/${cloudName}/image/upload/${params.join(',')}${src}`;
}

What this achieves:

  • Automatic format conversion - serves WebP to Chrome, AVIF to Safari, JPEG to older browsers
  • Responsive sizing - delivers exactly the image width needed for each device
  • CDN delivery - Cloudinary's edge network serves images from 60+ global locations
  • Zero Vercel costs - bypasses Vercel's image optimization quota entirely
  • Quality optimization - q_auto lets Cloudinary choose optimal quality per image

Impact: Average image size reduced by 60%, with zero manual effort per image.

03

Decision 3: CSS Architecture

We chose vanilla CSS over Tailwind CSS. Reasons:

  • No unused CSS classes shipped to the client (Tailwind's purge isn't perfect)
  • Complete control over the cascade and specificity
  • No build-time dependency or framework lock-in
  • Smaller final CSS bundle (our total CSS: ~45KB vs typical Tailwind: 80-120KB)

We organized CSS using a component-based file structure:

styles/
  globals.css           # Reset, variables, typography
  layout.css            # Grid system, containers
  components/
    hero.css
    services.css
    portfolio.css
04

Decision 4: Font Loading Strategy

Fonts are the silent performance killer. Our approach:

import { Inter } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',        // Show text immediately in fallback font
  preload: true,          // Preload font files in <head>
  variable: '--font-inter',
});

Impact: No flash of invisible text (FOIT), no layout shift from font swapping, and fonts load in parallel with page content.

05

Decision 5: Animation Strategy

We use AOS (Animate On Scroll) for entrance animations, but with careful performance guardrails:

  • IntersectionObserver - Animations only trigger when elements enter the viewport
  • will-change: transform - Hints the browser to GPU-accelerate specific elements
  • No JavaScript-heavy animation libraries - Avoided Framer Motion for this site (too much JavaScript for a content-focused site)
  • CSS transform and opacity only - These don't trigger layout recalculation

SEO Architecture

Our SEO implementation goes beyond basic metadata:

1. Centralized SEO Utility (lib/seo.ts)

A single file generates all metadata, JSON-LD schemas, and breadcrumbs:

export function buildMetadata(params: {
  title: string;
  description: string;
  keywords?: string;
  path: string;
}): Metadata {
  return {
    title: params.title,
    description: params.description,
    keywords: params.keywords,
    alternates: { canonical: `https://theaquarious.com${params.path}` },
    openGraph: { /* ... */ },
    twitter: { /* ... */ },
  };
}

2. JSON-LD on Every Page

  • Homepage: Organization schema
  • Service pages: Service schema + FAQ schema
  • Portfolio pages: CaseStudy / CreativeWork schema
  • All pages: BreadcrumbList schema

3. Internal Linking Strategy

Every service page links to 3+ related services. Every portfolio page links back to the relevant service. Blog posts (when live) will link to service pages. This creates a strong internal link graph that distributes page authority.

Results

MetricBefore RedesignAfter (Next.js)Improvement
PageSpeed Desktop6897+43%
PageSpeed Mobile5289+71%
LCP3.8s1.1s-71%
CLS0.220.03-86%
Total CSS Size180KB45KB-75%
Average Image Size450KB85KB-81%
JavaScript Sent to Client520KB195KB-63%
Time to First Byte850ms120ms-86%

Lessons Learned

1

Cloudinary fetch mode is powerful but underdocumented. The ability to proxy ANY external URL through Cloudinary for automatic optimization is a game-changer for sites with externally hosted images.

2

Vanilla CSS outperforms Tailwind for content-heavy sites. Tailwind adds convenience but also adds unused CSS and build complexity. For a marketing site where every byte matters, hand-crafted CSS wins.

3

Server Components are the biggest Next.js breakthrough. Moving from "sending React to the browser" to "rendering on the server and sending HTML" fundamentally changes the performance equation.

4

SEO architecture should be centralized. Our lib/seo.ts utility ensures consistency across 90+ pages. Without it, metadata quality would degrade as the site grows.

5

Start with performance, don't retrofit it. It's 10x easier to build a fast site than to make a slow site fast.

Want a website that scores 90+ on Google PageSpeed? Talk to our Next.js engineering team - we'll show you exactly what's possible.

Need a fast website?

Talk to our Next.js engineering team - we'll show you exactly what's possible.