Optimize portfolios for search engines and set up analytics. Covers meta tags, OG images, structured data, analytics options, and performance optimization.
Optimize the portfolio for search engines and set up analytics to track visitors.
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Name | Role',
description: 'One compelling sentence about what makes them unique.',
keywords: ['developer', 'portfolio', 'react', ...relevant terms],
authors: [{ name: 'Full Name' }],
creator: 'Full Name',
// Open Graph (social sharing)
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://example.com',
siteName: 'Name Portfolio',
title: 'Name | Role',
description: 'Same compelling description',
images: [{
url: '/og-image.png', // 1200x630px recommended
width: 1200,
height: 630,
alt: 'Name - Role'
}]
},
// Twitter Card
twitter: {
card: 'summary_large_image',
title: 'Name | Role',
description: 'Same compelling description',
images: ['/og-image.png'],
creator: '@twitterhandle'
},
// Robots
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true
}
}
}
Create /public/og-image.png (1200x630px) with:
Quick option: Use a solid color background with large text.
Add these to /public:
favicon.ico (32x32)apple-touch-icon.png (180x180)favicon-16x16.pngfavicon-32x32.pngIn layout.tsx:
export const metadata: Metadata = {
icons: {
icon: '/favicon.ico',
apple: '/apple-touch-icon.png',
},
}
Homepage: "Name | Role" or "Name - Tagline"
Example: "Jane Doe | Senior Frontend Engineer"
Example: "Jane Doe - Building beautiful web experiences"
Keep under 60 characters.
150-160 characters that:
- Say who they are
- Mention key skills/focus
- Include a hook
Example: "Frontend engineer specializing in React and TypeScript. Building fast, accessible web apps. Previously at Stripe, now freelancing."
Add to layout.tsx for rich search results:
export default function RootLayout({ children }) {
return (
<html>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Person',
name: 'Full Name',
url: 'https://example.com',
jobTitle: 'Role',
sameAs: [
'https://github.com/username',
'https://linkedin.com/in/username',
'https://twitter.com/username'
]
})
}}
/>
</head>
<body>{children}</body>
</html>
)
}
npm install @vercel/analytics
In layout.tsx:
import { Analytics } from '@vercel/analytics/react'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
</body>
</html>
)
}
Pros: Zero config on Vercel, privacy-friendly, free tier Enable: Vercel Dashboard โ Project โ Analytics tab
npm install @vercel/speed-insights
import { SpeedInsights } from '@vercel/speed-insights/next'
// Add alongside Analytics
<SpeedInsights />
// In layout.tsx <head>
<script
defer
data-domain="yourdomain.com"
src="https://plausible.io/js/script.js"
/>
Pros: Privacy-first, no cookie banner needed, simple dashboard Cost: $9/month or self-host free
npm install @next/third-parties
import { GoogleAnalytics } from '@next/third-parties/google'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<GoogleAnalytics gaId="G-XXXXXXXXXX" />
</body>
</html>
)
}
Note: May require cookie consent banner in EU.
For minimal tracking, create an API route:
// app/api/view/route.ts
export async function POST(request: Request) {
const { page } = await request.json()
// Log to your preferred service (database, file, etc.)
console.log(`Page view: ${page}`)
return Response.json({ success: true })
}
Add verification file:
Download googleXXXXXXX.html to /public
Or add meta tag to layout.tsx:
export const metadata: Metadata = {
verification: {
google: 'your-verification-code',
},
}
Create app/sitemap.ts:
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 1,
},
// Add additional pages if multi-page
]
}
Create app/robots.ts:
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: 'https://example.com/sitemap.xml',
}
}
import Image from 'next/image'
// Always use next/image for optimization
<Image
src="/photo.jpg"
alt="Descriptive alt text"
width={800}
height={600}
priority // For above-the-fold images
/>
Run Lighthouse audit:
Target scores:
Before Launch:
After Launch:
/publicnext/dynamic for heavy components