puffin-app/app/layout.tsx

112 lines
3.7 KiB
TypeScript
Raw Normal View History

Migrate from Vite to Next.js 16 with Turbopack This is a major migration from Vite to Next.js 16.0.1 for improved performance, better SEO, and modern React features. ## Next.js Migration Changes - Upgraded to Next.js 16.0.1 with Turbopack (from Vite 6) - Migrated from client-side routing to App Router architecture - Created app/ directory with Next.js page structure - Added server components and client components pattern - Configured standalone Docker builds for production ## Bug Fixes - React Hooks - Fixed infinite loop in Header.tsx scroll behavior (removed lastScrollY state dependency) - Fixed infinite loop in useCalculatorState.ts (wrapped saveState/clearState in useCallback) - Fixed infinite loop in OffsetOrder.tsx (removed savedState from useEffect dependencies) - Removed unused React imports from all client components ## Environment Variable Migration - Migrated all VITE_ variables to NEXT_PUBLIC_ prefix - Updated src/utils/config.ts to use direct static references (required for Next.js) - Updated src/api/checkoutClient.ts, emailClient.ts, aisClient.ts for Next.js env vars - Updated src/vite-env.d.ts types for Next.js environment - Maintained backward compatibility with Docker window.env ## Layout & UX Improvements - Fixed footer to always stay at bottom of viewport using flexbox - Updated app/layout.tsx with flex-1 main content area - Preserved glass morphism effects and luxury styling ## TypeScript & Build - Fixed TypeScript strict mode compilation errors - Removed unused imports and variables - Fixed Axios interceptor types in project/src/api/wrenClient.ts - Production build verified and passing ## Testing & Verification - Tested calculator end-to-end in Playwright - Verified Wren API integration working (11 portfolios fetched) - Confirmed calculation: 5000L → 13.47 tons CO₂ → $3,206 total - All navigation routes working correctly - Footer positioning verified across all pages ## Files Added - app/ directory with Next.js routes - components/ directory with client components - next.config.mjs, next-env.d.ts - ENV_MIGRATION.md, NEXTJS_MIGRATION_COMPLETE.md documentation ## Files Modified - Docker configuration for Next.js standalone builds - package.json dependencies (Next.js, React 19) - ts config.json for Next.js - All API clients for new env var pattern 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 22:23:45 +01:00
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import Script from 'next/script';
import { Header } from '../components/Header';
import { Footer } from '../components/Footer';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: {
default: 'Puffin Offset - Carbon Offsetting for Yachts',
template: '%s | Puffin Offset',
},
description: 'Premium carbon offset calculator and solutions for luxury yachts. Offset your vessel\'s carbon footprint with verified climate projects.',
keywords: ['carbon offset', 'yacht carbon offset', 'luxury yacht sustainability', 'marine carbon calculator', 'yacht emissions', 'climate action'],
authors: [{ name: 'Puffin Offset' }],
creator: 'Puffin Offset',
publisher: 'Puffin Offset',
metadataBase: new URL(process.env.NEXT_PUBLIC_API_BASE_URL || 'https://puffinoffset.com'),
alternates: {
canonical: '/',
},
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://puffinoffset.com',
title: 'Puffin Offset - Carbon Offsetting for Yachts',
description: 'Premium carbon offset calculator and solutions for luxury yachts. Offset your vessel\'s carbon footprint with verified climate projects.',
siteName: 'Puffin Offset',
images: [
{
url: '/puffinOffset.png',
width: 1200,
height: 630,
alt: 'Puffin Offset Logo',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Puffin Offset - Carbon Offsetting for Yachts',
description: 'Premium carbon offset calculator and solutions for luxury yachts.',
images: ['/puffinOffset.png'],
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
verification: {
// Add Google Search Console verification here when available
// google: 'your-verification-code',
},
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={inter.className}>
<head>
{/* Preconnect to external domains for performance */}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
{/* Favicon and app icons */}
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/puffinOffset.png" />
{/* PWA manifest */}
<link rel="manifest" href="/manifest.json" />
{/* Theme color */}
<meta name="theme-color" content="#1E40AF" />
</head>
<body className="flex flex-col min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-cyan-50 wave-pattern">
{/* Google Analytics - using Next.js Script component for security and performance */}
{process.env.NODE_ENV === 'production' && (
<>
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
`}
</Script>
</>
)}
<Header />
<main className="flex-1 max-w-[1600px] w-full mx-auto pt-24 pb-8 sm:pb-12 px-4 sm:px-6 lg:px-8 overflow-hidden">
{children}
</main>
<Footer />
</body>
</html>
);
}