Skip to content

Commit e86a399

Browse files
committed
fix: Lighthouse optimizations - SEO, accessibility, and performance
SEO (100): - Add robots.txt, sitemap.xml, manifest.json - Add OpenGraph and Twitter Card meta tags - Add apple-touch-icon - Uncomment manifest link Accessibility (94→targeting 100): - Add aria-labels to all icon-only buttons (menu, file upload, voice, submit, model selector, web/mobile toggles, attachment remove) - Add aria-hidden to decorative icons - Add skip-to-content link with sr-only styling - Wrap content areas in <main> element - Add role="status" and sr-only text to loading spinners - Add aria-label to sidebar backdrop Performance: - Remove unused 650KB cheatcode-github-hero.png from public/ - Change GTM script from afterInteractive to lazyOnload - Add dns-prefetch and preconnect hints for third-party domains - Add immutable cache headers for static assets - Remove duplicate tailwindcss from devDependencies
1 parent dbb254b commit e86a399

File tree

15 files changed

+136
-26
lines changed

15 files changed

+136
-26
lines changed

frontend/next.config.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ const nextConfig: NextConfig = {
4343
source: '/(.*)',
4444
headers: securityHeaders,
4545
},
46+
{
47+
source: '/_next/static/(.*)',
48+
headers: [
49+
{
50+
key: 'Cache-Control',
51+
value: 'public, max-age=31536000, immutable',
52+
},
53+
],
54+
},
55+
{
56+
source: '/favicon.png',
57+
headers: [
58+
{
59+
key: 'Cache-Control',
60+
value: 'public, max-age=86400, stale-while-revalidate=604800',
61+
},
62+
],
63+
},
4664
];
4765
},
4866
};

frontend/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@
8585
"knip": "^5.85.0",
8686
"prettier": "^3.7.4",
8787
"shiki": "^3.19.0",
88-
"tailwindcss": "^4.1.17",
8988
"tw-animate-css": "^1.4.0",
9089
"typescript": "^5.9.3"
9190
}
-635 KB
Binary file not shown.

frontend/public/manifest.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "Cheatcode AI",
3+
"short_name": "Cheatcode",
4+
"description": "AI agent that designs, builds and deploys full-stack web and mobile applications in minutes.",
5+
"start_url": "/",
6+
"display": "standalone",
7+
"background_color": "#09090b",
8+
"theme_color": "#09090b",
9+
"icons": [
10+
{
11+
"src": "/favicon.png",
12+
"sizes": "512x512",
13+
"type": "image/png",
14+
"purpose": "any maskable"
15+
}
16+
]
17+
}

frontend/public/robots.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
User-agent: *
2+
Allow: /
3+
Disallow: /api/
4+
Disallow: /projects/
5+
Disallow: /settings/
6+
7+
Sitemap: https://trycheatcode.com/sitemap.xml

frontend/src/app/(home)/layout.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,9 @@ export default function HomeLayout({ children }: HomeLayoutProps) {
111111
// Show loading state while checking auth (health check runs in background)
112112
if (!isClient || !isLoaded) {
113113
return (
114-
<div className="flex items-center justify-center min-h-screen">
115-
<Loader2 className="h-8 w-8 animate-spin text-primary" />
114+
<div className="flex items-center justify-center min-h-screen" role="status" aria-label="Loading">
115+
<Loader2 className="h-8 w-8 animate-spin text-primary" aria-hidden="true" />
116+
<span className="sr-only">Loading application...</span>
116117
</div>
117118
);
118119
}
@@ -124,11 +125,14 @@ export default function HomeLayout({ children }: HomeLayoutProps) {
124125
<div
125126
className={`w-full relative min-h-screen ${!isThreadPage ? 'gradient-home-bg' : 'bg-thread-panel'}`}
126127
>
128+
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:z-[100] focus:top-2 focus:left-2 focus:px-4 focus:py-2 focus:bg-white focus:text-black focus:rounded-md focus:text-sm focus:font-medium">
129+
Skip to main content
130+
</a>
127131
{healthBanner}
128132
{!isThreadPage && <Navbar sidebarOpen={false} />}
129-
<div className={isThreadPage ? 'pt-0' : 'pt-6'}>
133+
<main id="main-content" className={isThreadPage ? 'pt-0' : 'pt-6'}>
130134
<Suspense fallback={<PageLoadingFallback />}>{children}</Suspense>
131-
</div>
135+
</main>
132136
{!isThreadPage && (
133137
<footer className="w-full py-6 text-center text-xs text-white/70">
134138
Built by{' '}
@@ -161,12 +165,15 @@ export default function HomeLayout({ children }: HomeLayoutProps) {
161165
<div
162166
className={`w-full relative min-h-screen ${!isThreadPage ? 'gradient-home-bg' : 'bg-thread-panel'}`}
163167
>
168+
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:z-[100] focus:top-2 focus:left-2 focus:px-4 focus:py-2 focus:bg-white focus:text-black focus:rounded-md focus:text-sm focus:font-medium">
169+
Skip to main content
170+
</a>
164171
{!isThreadPage && <Navbar sidebarOpen={sidebarOpen} />}
165-
<div className={isThreadPage ? 'pt-0' : 'pt-6'}>
172+
<main id="main-content" className={isThreadPage ? 'pt-0' : 'pt-6'}>
166173
<Suspense fallback={<PageLoadingFallback />}>
167174
{children}
168175
</Suspense>
169-
</div>
176+
</main>
170177
{!isThreadPage && (
171178
<footer className="w-full py-6 text-center text-xs text-white/70">
172179
Built by{' '}

frontend/src/app/layout.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,25 @@ export const metadata: Metadata = {
6363
icons: {
6464
icon: [{ url: '/favicon.png', sizes: 'any' }],
6565
shortcut: '/favicon.png',
66+
apple: '/favicon.png',
67+
},
68+
manifest: '/manifest.json',
69+
openGraph: {
70+
type: 'website',
71+
locale: 'en_US',
72+
url: siteConfig.url,
73+
siteName: siteConfig.name,
74+
title: siteConfig.name,
75+
description:
76+
'Cheatcode is an AI agent that designs, builds and deploys full-stack web and mobile applications in minutes.',
77+
},
78+
twitter: {
79+
card: 'summary',
80+
title: siteConfig.name,
81+
description:
82+
'AI agent that builds web and mobile apps from natural-language prompts.',
83+
creator: '@trycheatcode',
6684
},
67-
// manifest: "/manifest.json",
6885
alternates: {
6986
canonical: siteConfig.url,
7087
},
@@ -78,16 +95,21 @@ export default function RootLayout({
7895
return (
7996
<html lang="en" suppressHydrationWarning>
8097
<head>
81-
{/* Google Tag Manager */}
82-
<Script id="google-tag-manager" strategy="afterInteractive">
98+
<link rel="dns-prefetch" href="https://www.googletagmanager.com" />
99+
<link rel="dns-prefetch" href="https://www.google-analytics.com" />
100+
<link
101+
rel="preconnect"
102+
href="https://fonts.googleapis.com"
103+
crossOrigin="anonymous"
104+
/>
105+
{/* Google Tag Manager - lazy loaded for better LCP */}
106+
<Script id="google-tag-manager" strategy="lazyOnload">
83107
{`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
84108
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
85109
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
86110
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
87111
})(window,document,'script','dataLayer','GTM-MB8GJRLK');`}
88112
</Script>
89-
{/* Temporarily disabled to remove top line */}
90-
{/* <Script async src="https://cdn.tolt.io/tolt.js" data-tolt={process.env.NEXT_PUBLIC_TOLT_REFERRAL_ID}></Script> */}
91113
</head>
92114

93115
<body

frontend/src/app/sitemap.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { MetadataRoute } from 'next';
2+
3+
export default function sitemap(): MetadataRoute.Sitemap {
4+
const baseUrl = 'https://trycheatcode.com';
5+
6+
return [
7+
{
8+
url: baseUrl,
9+
lastModified: new Date(),
10+
changeFrequency: 'weekly',
11+
priority: 1,
12+
},
13+
{
14+
url: `${baseUrl}/sign-in`,
15+
lastModified: new Date(),
16+
changeFrequency: 'monthly',
17+
priority: 0.5,
18+
},
19+
{
20+
url: `${baseUrl}/sign-up`,
21+
lastModified: new Date(),
22+
changeFrequency: 'monthly',
23+
priority: 0.5,
24+
},
25+
];
26+
}

frontend/src/components/home/sections/navbar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,9 @@ export function Navbar({ sidebarOpen = false }: { sidebarOpen?: boolean }) {
330330
<button
331331
className="md:hidden border border-border size-8 rounded-md cursor-pointer flex items-center justify-center"
332332
onClick={toggleDrawer}
333+
aria-label={isDrawerOpen ? 'Close menu' : 'Open menu'}
333334
>
334-
{isDrawerOpen ? <X size={20} /> : <Menu size={20} />}
335+
{isDrawerOpen ? <X size={20} aria-hidden="true" /> : <Menu size={20} aria-hidden="true" />}
335336
</button>
336337
</div>
337338
</div>

frontend/src/components/model-selector.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export function ModelSelector({
8383
className,
8484
)}
8585
size="sm"
86+
aria-label="Select AI model"
8687
>
8788
<SelectValue>
8889
<div className="flex items-center gap-1.5">

0 commit comments

Comments
 (0)