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:
97+ PageSpeed score - on desktop, 85+ on mobile
Full SEO architecture - SSR, JSON-LD schema on every page, optimized metadata
Rich visual design - animations, gradients, partner showcases with images
Image-heavy portfolio - 30+ case studies with high-quality screenshots
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
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.
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.
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.cssDecision 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.
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
| Metric | Before Redesign | After (Next.js) | Improvement |
|---|---|---|---|
| PageSpeed Desktop | 68 | 97 | +43% |
| PageSpeed Mobile | 52 | 89 | +71% |
| LCP | 3.8s | 1.1s | -71% |
| CLS | 0.22 | 0.03 | -86% |
| Total CSS Size | 180KB | 45KB | -75% |
| Average Image Size | 450KB | 85KB | -81% |
| JavaScript Sent to Client | 520KB | 195KB | -63% |
| Time to First Byte | 850ms | 120ms | -86% |
Lessons Learned
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.
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.
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.
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.
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.


