SEO: metadata, sitemap, robots, OpenGraph, per-page titles + API stream field mapping
This commit is contained in:
parent
8f4e5dea40
commit
86ce153ad2
@ -2,6 +2,15 @@ import React from 'react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Eye, Mail, Users, Heart, ExternalLink } from 'lucide-react';
|
import { Eye, Mail, Users, Heart, ExternalLink } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'About CCFW',
|
||||||
|
description: 'Learn about Cape Coral Friends of Wildlife and our mission to protect Florida wildlife.',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-5xl mx-auto px-6 py-16 space-y-20">
|
<div className="max-w-5xl mx-auto px-6 py-16 space-y-20">
|
||||||
|
|||||||
@ -3,6 +3,15 @@ import { Heart, Shield, Camera, TreePine } from 'lucide-react';
|
|||||||
import { api, Campaign } from '@/lib/api';
|
import { api, Campaign } from '@/lib/api';
|
||||||
import DonationCard from '../components/DonationCard';
|
import DonationCard from '../components/DonationCard';
|
||||||
|
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Donate',
|
||||||
|
description: 'Support burrowing owl conservation with a donation to CCFW.',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const FALLBACK_CAMPAIGNS: Campaign[] = [
|
const FALLBACK_CAMPAIGNS: Campaign[] = [
|
||||||
{
|
{
|
||||||
id: '1', title: 'Land Preservation Fund',
|
id: '1', title: 'Land Preservation Fund',
|
||||||
|
|||||||
@ -3,6 +3,15 @@ import { Calendar } from 'lucide-react';
|
|||||||
import { api, Event } from '@/lib/api';
|
import { api, Event } from '@/lib/api';
|
||||||
import EventCard from '../components/EventCard';
|
import EventCard from '../components/EventCard';
|
||||||
|
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Events',
|
||||||
|
description: 'Join CCFW conservation events, bird walks, and community programs.',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const FALLBACK_EVENTS: Event[] = [
|
const FALLBACK_EVENTS: Event[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
|
|||||||
@ -1,11 +1,44 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata, Viewport } from 'next';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
import Navbar from './components/Navbar';
|
import Navbar from './components/Navbar';
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
width: 'device-width',
|
||||||
|
initialScale: 1,
|
||||||
|
themeColor: '#0a1a15',
|
||||||
|
};
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Owl Stream | Cape Coral Friends of Wildlife',
|
title: {
|
||||||
description: 'Live burrowing owl cams, wildlife conservation, and nature in Cape Coral, Florida.',
|
default: 'Owl Stream | Cape Coral Friends of Wildlife',
|
||||||
|
template: '%s | Owl Stream',
|
||||||
|
},
|
||||||
|
description: 'Live burrowing owl cams, wildlife conservation, and nature in Cape Coral, Florida. Watch 24/7 streams, donate, and volunteer with CCFW.',
|
||||||
|
keywords: ['burrowing owl', 'cape coral', 'wildlife camera', 'live stream', 'conservation', 'Florida wildlife', 'CCFW'],
|
||||||
|
authors: [{ name: 'Cape Coral Friends of Wildlife' }],
|
||||||
|
openGraph: {
|
||||||
|
type: 'website',
|
||||||
|
locale: 'en_US',
|
||||||
|
siteName: 'Owl Stream',
|
||||||
|
title: 'Owl Stream — Live Burrowing Owl Cams',
|
||||||
|
description: 'Watch Cape Coral\'s burrowing owls live, 24/7. Free wildlife cameras by CCFW.',
|
||||||
|
images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'Owl Stream - Live Wildlife Cameras' }],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: 'summary_large_image',
|
||||||
|
title: 'Owl Stream — Live Burrowing Owl Cams',
|
||||||
|
description: 'Watch Cape Coral\'s burrowing owls live, 24/7.',
|
||||||
|
images: ['/og-image.png'],
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
icon: '/favicon.svg',
|
||||||
|
apple: '/apple-touch-icon.png',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Map } from 'lucide-react';
|
import { Map } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Burrow Map',
|
||||||
|
description: 'Interactive map of burrowing owl burrows in Cape Coral, Florida.',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function MapPage() {
|
export default function MapPage() {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto px-6 py-16 space-y-10">
|
<div className="max-w-7xl mx-auto px-6 py-16 space-y-10">
|
||||||
|
|||||||
11
app/robots.ts
Normal file
11
app/robots.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { MetadataRoute } from 'next';
|
||||||
|
|
||||||
|
export default function robots(): MetadataRoute.Robots {
|
||||||
|
return {
|
||||||
|
rules: {
|
||||||
|
userAgent: '*',
|
||||||
|
allow: '/',
|
||||||
|
},
|
||||||
|
sitemap: 'https://owlstream.bizzle.cloud/sitemap.xml',
|
||||||
|
};
|
||||||
|
}
|
||||||
17
app/sitemap.ts
Normal file
17
app/sitemap.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { MetadataRoute } from 'next';
|
||||||
|
|
||||||
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
|
const base = 'https://owlstream.bizzle.cloud';
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ url: base, lastModified: now, changeFrequency: 'daily', priority: 1 },
|
||||||
|
{ url: `${base}/streams`, lastModified: now, changeFrequency: 'hourly', priority: 0.9 },
|
||||||
|
{ url: `${base}/wildlife`, lastModified: now, changeFrequency: 'monthly', priority: 0.8 },
|
||||||
|
{ url: `${base}/donate`, lastModified: now, changeFrequency: 'monthly', priority: 0.8 },
|
||||||
|
{ url: `${base}/volunteer`, lastModified: now, changeFrequency: 'monthly', priority: 0.7 },
|
||||||
|
{ url: `${base}/events`, lastModified: now, changeFrequency: 'weekly', priority: 0.7 },
|
||||||
|
{ url: `${base}/map`, lastModified: now, changeFrequency: 'weekly', priority: 0.7 },
|
||||||
|
{ url: `${base}/about`, lastModified: now, changeFrequency: 'monthly', priority: 0.5 },
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -3,6 +3,15 @@ import { Radio } from 'lucide-react';
|
|||||||
import { api, Stream } from '@/lib/api';
|
import { api, Stream } from '@/lib/api';
|
||||||
import StreamCard from '../components/StreamCard';
|
import StreamCard from '../components/StreamCard';
|
||||||
|
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Live Cams',
|
||||||
|
description: 'Watch live wildlife cameras streaming 24/7 from Cape Coral.',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function getStreams(): Promise<Stream[]> {
|
async function getStreams(): Promise<Stream[]> {
|
||||||
try { return await api.getStreams(); }
|
try { return await api.getStreams(); }
|
||||||
catch { return []; }
|
catch { return []; }
|
||||||
|
|||||||
@ -2,6 +2,15 @@ import React from 'react';
|
|||||||
import { Lock, Users, ClipboardList } from 'lucide-react';
|
import { Lock, Users, ClipboardList } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Volunteer',
|
||||||
|
description: 'Join the CCFW volunteer team for owl surveys and habitat restoration.',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function VolunteerPage() {
|
export default function VolunteerPage() {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-5xl mx-auto px-6 py-16 space-y-12">
|
<div className="max-w-5xl mx-auto px-6 py-16 space-y-12">
|
||||||
|
|||||||
@ -3,6 +3,15 @@ import { TreePine } from 'lucide-react';
|
|||||||
import { api, Wildlife } from '@/lib/api';
|
import { api, Wildlife } from '@/lib/api';
|
||||||
import WildlifeCard from '../components/WildlifeCard';
|
import WildlifeCard from '../components/WildlifeCard';
|
||||||
|
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Wildlife Guide',
|
||||||
|
description: 'Discover Cape Coral wildlife including burrowing owls and gopher tortoises.',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Fallback data when API unavailable
|
// Fallback data when API unavailable
|
||||||
const FALLBACK: Wildlife[] = [
|
const FALLBACK: Wildlife[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user