Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/lhci-desktop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"ci": {
"collect": {
"numberOfRuns": 1,
"settings": {
"preset": "desktop"
}
}
}
}
105 changes: 105 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,108 @@ jobs:
- run: pnpm format:check
- run: pnpm test:a11y
- run: pnpm build

lighthouse:
name: Lighthouse Audit
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build

- name: Run Lighthouse on Mobile
id: lighthouse_mobile
uses: treosh/lighthouse-ci-action@v12
with:
urls: |
http://localhost:4173/
startServerCommand: pnpm preview
uploadArtifacts: true
temporaryPublicStorage: true

- name: Save Mobile Manifest
run: cp -r .lighthouseci .lighthouseci-mobile

- name: Run Lighthouse on Desktop
id: lighthouse_desktop
uses: treosh/lighthouse-ci-action@v12
with:
urls: |
http://localhost:4173/
startServerCommand: pnpm preview
uploadArtifacts: true
temporaryPublicStorage: true
configPath: './.github/lhci-desktop.json'

- name: Save Desktop Manifest
run: cp -r .lighthouseci .lighthouseci-desktop

- name: Post Comment on PR
uses: actions/github-script@v7
if: github.event_name == 'pull_request'
env:
MOBILE_LINKS: ${{ steps.lighthouse_mobile.outputs.links }}
DESKTOP_LINKS: ${{ steps.lighthouse_desktop.outputs.links }}
with:
script: |
const fs = require('fs');
const mobileManifest = JSON.parse(fs.readFileSync('.lighthouseci-mobile/manifest.json', 'utf8'));
const desktopManifest = JSON.parse(fs.readFileSync('.lighthouseci-desktop/manifest.json', 'utf8'));

const mobile = mobileManifest[0];
const desktop = desktopManifest[0];

const formatScore = (val) => Math.round(val * 100);

const mobilePerf = formatScore(mobile.summary.performance);
const mobileA11y = formatScore(mobile.summary.accessibility);
const mobileBp = formatScore(mobile.summary['best-practices']);
const mobileSeo = formatScore(mobile.summary.seo);

const desktopPerf = formatScore(desktop.summary.performance);
const desktopA11y = formatScore(desktop.summary.accessibility);
const desktopBp = formatScore(desktop.summary['best-practices']);
const desktopSeo = formatScore(desktop.summary.seo);

const getScoreEmoji = (score) => {
if (score >= 95) return '🟢';
if (score >= 90) return '🟡';
return '🔴';
};

const mobileLinks = JSON.parse(process.env.MOBILE_LINKS || '{}');
const desktopLinks = JSON.parse(process.env.DESKTOP_LINKS || '{}');
const mobileReport = mobileLinks['http://localhost:4173/'] || '';
const desktopReport = desktopLinks['http://localhost:4173/'] || '';

const reportLinks = [];
if (mobileReport) reportLinks.push(`[Mobile Report](${mobileReport})`);
if (desktopReport) reportLinks.push(`[Desktop Report](${desktopReport})`);
const reportText = reportLinks.length > 0 ? `📄 **Reports:** ${reportLinks.join(' | ')}` : '';

const body = `### ⚡ Lighthouse Audit Results

| Category | Mobile | Desktop | Target |
| --- | --- | --- | --- |
| **Performance** | ${getScoreEmoji(mobilePerf)} **${mobilePerf}** | ${getScoreEmoji(desktopPerf)} **${desktopPerf}** | >= 95 |
| **Accessibility** | ${getScoreEmoji(mobileA11y)} **${mobileA11y}** | ${getScoreEmoji(desktopA11y)} **${desktopA11y}** | >= 95 |
| **Best Practices** | ${getScoreEmoji(mobileBp)} **${mobileBp}** | ${getScoreEmoji(desktopBp)} **${desktopBp}** | >= 95 |
| **SEO** | ${getScoreEmoji(mobileSeo)} **${mobileSeo}** | ${getScoreEmoji(desktopSeo)} **${desktopSeo}** | >= 95 |

${reportText}

*Audit ran against the latest build on preview server.*`;

github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
});
96 changes: 96 additions & 0 deletions docs/PERF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Lighthouse & Core Web Vitals Optimization Report

This document records the performance auditing and optimization process for the Wraith Protocol website, detailing the baseline metrics, optimization strategies, and post-fix results.

## Summary of Results

We successfully reached a score of **95+** on all four Lighthouse axes (Performance, Accessibility, Best Practices, SEO) for both mobile and desktop views, with no regressions in layout shifts (CLS), largest contentful paint (LCP), or input latency (INP).

| Axis | Baseline Mobile | Optimized Mobile | Baseline Desktop | Optimized Desktop | Target | Status |
| ------------------ | --------------- | ---------------- | ---------------- | ----------------- | ------ | --------- |
| **Performance** | 63 | **96** | 98 | **100** | >= 95 | 🟢 Passed |
| **Accessibility** | 100 | **100** | 100 | **100** | >= 95 | 🟢 Passed |
| **Best Practices** | 100 | **100** | 100 | **100** | >= 95 | 🟢 Passed |
| **SEO** | 100 | **100** | 100 | **100** | >= 95 | 🟢 Passed |

### Core Web Vitals Metrics

| Metric | Baseline Mobile | Optimized Mobile | Baseline Desktop | Optimized Desktop | Status |
| ---------------------------------- | --------------- | ---------------- | ---------------- | ----------------- | ----------- |
| **First Contentful Paint (FCP)** | 4.7 s | 1.7 s | 0.9 s | 0.4 s | 🟢 Improved |
| **Largest Contentful Paint (LCP)** | 5.2 s | 2.6 s | 1.0 s | 0.7 s | 🟢 Improved |
| **Total Blocking Time (TBT)** | 210 ms | 110 ms | 0 ms | 0 ms | 🟢 Improved |
| **Cumulative Layout Shift (CLS)** | 0 | 0.011 | 0.002 | 0 | 🟢 Stable |
| **Speed Index (SI)** | 6.5 s | 2.0 s | 0.9 s | 0.4 s | 🟢 Improved |

---

## Diagnostics & Identified Issues

During the baseline audit, the following key performance bottlenecks were identified:

1. **Render-Blocking Web Fonts CDN**: The external Google Fonts CDN links for Inter, JetBrains Mono, and Space Grotesk blocked rendering, adding ~1.3 seconds of latency on mobile.
2. **Heavy SDK Bundled in Initial Load**: The homepage metrics component imported `@wraith-protocol/sdk/chains/stellar` which in turn imported the massive `@stellar/stellar-sdk` library. This bundled 1.2+ MB of raw JavaScript into the main chunk, delaying download and execution.
3. **Unsized and Unoptimized Images**:
- The brand `logo.png` had a resolution of 1288x1020 but was displayed at `h-6` (24px height).
- Brand and partner logo image tags lacked explicit `width` and `height` attributes, leading to layout shifts and Lighthouse warnings.
4. **No Critical CSS Inlining**: The compiled Tailwind CSS stylesheet was loaded as an external link, causing additional render-blocking roundtrips.

---

## Optimizations Implemented

We resolved these issues through the following steps:

### 1. Font Self-Hosting & Subsetting

We downloaded the modern `.woff2` files for only the required weights and the `latin` character subset.

- **Space Grotesk**: 400, 500, 600, 700
- **Inter**: 400, 500, 600, 700
- **JetBrains Mono**: 400, 500, 700
- Embedded `@font-face` definitions directly inside `src/index.css` with `font-display: swap` to prevent FOIT (Flash of Invisible Text).
- Preloaded critical fonts (Space Grotesk 700, Inter 400, Inter 600) in `index.html` to initiate downloads immediately.

### 2. Complete Tree-Shaking of Stellar and Wraith SDKs

The metrics component `src/components/StellarMetrics.tsx` imported `@wraith-protocol/sdk` solely to read deployment configurations (RPC URL and contract ID).

- We hardcoded these static configs locally in the file, removing the SDK import.
- This allowed Rollup/Vite to completely tree-shake `@wraith-protocol/sdk` and its massive `@stellar/stellar-sdk` dependency from the landing page.
- **Result:** Bundle size dropped from **1.23 MB** to **204 KB** (gzip: 62.89 KB).

### 3. Aggressive Code Splitting

- Implemented lazy loading via `React.lazy` and `Suspense` for all below-the-fold homepage components (Architecture, ForDevelopers, Chains, StellarMetrics, Compare, Showcase, EcosystemPartners, CtaStrip, and Footer).
- Implemented lazy loading for secondary pages (FAQ, Privacy, UseCases, Stellar, NotFound).
- This keeps the main initial JS chunk focused exclusively on the Header and Hero section.

### 4. Above-the-Fold Pre-rendering Skeleton

To speed up FCP and LCP, we pre-rendered the static HTML structure of the Header and Hero components inside the `<div id="root">` of `index.html`.

- This ensures the browser paints the static skeleton layout instantly upon downloading the HTML document (using the inlined CSS).
- React hydrates and replaces it seamlessly once the JS loads, with no visual layout shifts.

### 5. Critical CSS Inlining

- We created a node build script `scripts/inline-css.js` that compiles the production build, reads the compiled CSS file, injects it into a `<style>` block in the HTML `<head>`, and deletes the separate stylesheet asset.
- This eliminates all render-blocking CSS network roundtrips.

### 6. Image Resizing & Sizing Attributes

- Resized the oversized `logo.png` brand logo to a web-optimized resolution of 60x48 pixels, dropping file size from **35 KB** to **2.4 KB** (a 93% reduction).
- Added explicit `width` and `height` attributes to the brand logo, partner logos, and footer logos to prevent CLS.

---

## Pull Request Continuous Integration

We integrated a pull request Lighthouse audit gate into `.github/workflows/ci.yml`.

- Runs on push to `main` and `develop` and on all PRs.
- Builds the optimized assets.
- Executes Lighthouse CI (LHCI) on mobile and desktop profiles.
- Uploads HTML reports to temporary public storage.
- Posts a summary table with score cards and links to HTML reports as comments on pull requests.
97 changes: 92 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,26 @@
};
</script>

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Space+Grotesk:wght@400;500;600;700&display=swap"
rel="stylesheet"
rel="preload"
href="/fonts/space-grotesk-700-normal.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/fonts/inter-400-normal.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/fonts/inter-600-normal.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<script type="application/ld+json">
{
Expand Down Expand Up @@ -110,7 +125,79 @@
document.documentElement.setAttribute('data-theme', theme);
})();
</script>
<div id="root"></div>
<div id="root">
<div class="bg-surface text-on-surface">
<header
class="fixed top-0 z-50 w-full border-b border-outline-variant-30 bg-surface/80 backdrop-blur-sm"
>
<div class="mx-auto flex w-full items-center justify-between px-12 py-5">
<a href="https://usewraith.xyz" class="flex items-center gap-3">
<img src="/logo.png" alt="" width="30" height="24" class="h-6 opacity-90" />
<span class="font-heading text-[15px] font-bold tracking-[2px] text-on-surface"
>WRAITH</span
>
</a>
<div class="flex items-center gap-4">
<a
href="https://demo.usewraith.xyz"
target="_blank"
rel="noopener noreferrer"
class="hidden h-9 items-center justify-center bg-primary px-5 font-heading text-[11px] font-semibold uppercase tracking-[1.5px] text-surface md:flex"
>Launch App</a
>
</div>
</div>
</header>
<main id="main-content" tabindex="-1">
<section
class="flex w-full flex-col gap-16 px-6 pt-24 pb-[120px] md:flex-row md:gap-16 md:px-12 md:pt-24 lg:gap-16"
>
<div class="flex w-full flex-col gap-8 pt-16 md:w-1/2">
<div class="flex flex-wrap items-center gap-3">
<div class="flex items-center gap-2 border border-outline-variant px-2.5 py-1.5">
<span class="h-1.5 w-1.5 rounded-full bg-tertiary"></span>
<span
class="font-mono text-[10px] font-semibold tracking-[1.5px] text-on-surface-variant"
>LIVE ON 4 TESTNETS</span
>
</div>
<div class="rounded-sm border border-tertiary px-2.5 py-1.5">
<span
class="font-mono text-[10px] font-semibold uppercase tracking-[1.5px] text-tertiary"
>Stellar partner</span
>
</div>
</div>
<h1
class="font-heading text-[40px] font-bold leading-[1.05] tracking-[-2.5px] text-on-surface sm:text-[56px] md:text-[72px]"
>
Private payments<br />for every chain.
</h1>
<p class="font-body text-[17px] leading-[1.6] text-on-surface-variant">
A stealth-address toolkit built on ERC-5564 and ERC-6538. Drop it into your app and
send receiver-unlinkable payments across Horizen, Stellar, Solana, and CKB.
</p>
<div class="flex flex-wrap items-center gap-3">
<a
href="https://docs.usewraith.xyz"
target="_blank"
rel="noopener noreferrer"
class="flex h-12 items-center justify-center bg-primary px-7 font-heading text-[13px] font-semibold uppercase tracking-[1.5px] text-surface"
>Read the Docs</a
>
<a
href="https://demo.usewraith.xyz"
target="_blank"
rel="noopener noreferrer"
class="flex h-12 items-center justify-center border border-outline-variant px-7 font-heading text-[13px] font-semibold uppercase tracking-[1.5px] text-primary"
>Try the Demo</a
>
</div>
</div>
</section>
</main>
</div>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build && tsx scripts/og.ts && tsx scripts/sitemap.ts",
"build": "tsc -b && vite build && node scripts/inline-css.js && tsx scripts/og.ts && tsx scripts/sitemap.ts",
"og:generate": "tsx scripts/og.ts",
"preview": "vite preview",
"test": "vitest run",
Expand Down
2 changes: 0 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading