Etter SEO step 1

This commit is contained in:
Erol 2026-04-12 22:07:51 +02:00
parent b5f9f52ffe
commit 3bf429a5f7
11 changed files with 680 additions and 74 deletions

View file

@ -0,0 +1,16 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
robots: {
index: false,
follow: false,
googleBot: {
index: false,
follow: false,
},
},
};
export default function AdminLayout({ children }: { children: React.ReactNode }) {
return children;
}

74
frontend/src/app/golfbaner/[slug]/page.tsx Normal file → Executable file
View file

@ -1,17 +1,79 @@
// page.tsx import type { Metadata } from "next";
import { API_URL } from "@/config/constants"; import { API_URL } from "@/config/constants";
import {
createBreadcrumbJsonLd,
createFacilityJsonLd,
createPageMetadata,
trimDescription,
} from "@/app/seo";
import FacilityDetailView from "./FacilityDetailView"; import FacilityDetailView from "./FacilityDetailView";
export default async function GolfCoursePage({ params }: { params: Promise<{ slug: string }> }) { type GolfCoursePageProps = {
params: Promise<{ slug: string }>;
};
async function getFacility(slug: string) {
const res = await fetch(`${API_URL}/facilities/${slug}`, { cache: "no-store" });
return res.json();
}
export async function generateMetadata({ params }: GolfCoursePageProps): Promise<Metadata> {
const { slug } = await params; const { slug } = await params;
const res = await fetch(`${API_URL}/facilities/${slug}`, { cache: 'no-store' }); try {
const facility = await res.json(); const facility = await getFacility(slug);
if (!facility || facility.error) {
return createPageMetadata({
title: "Golfbane ikke funnet",
description: "Golfanlegget du prøvde å åpne finnes ikke på TeeOff.",
path: `/golfbaner/${slug}`,
});
}
const title = `${facility.name}${facility.city ? ` i ${facility.city}` : ""}`;
const fallbackDescription = `${facility.name} på TeeOff med banestatus, priser, kontaktinfo og lenker til nyttige ressurser.`;
return createPageMetadata({
title,
description: trimDescription(facility.description) || fallbackDescription,
path: `/golfbaner/${slug}`,
image: facility.image_url,
});
} catch {
return createPageMetadata({
title: "Golfbane",
description: "Golfanlegg på TeeOff med status, priser og klubbinfo.",
path: `/golfbaner/${slug}`,
});
}
}
export default async function GolfCoursePage({ params }: GolfCoursePageProps) {
const { slug } = await params;
const facility = await getFacility(slug);
if (!facility || facility.error) { if (!facility || facility.error) {
return <div className="p-20 text-center font-bold text-2xl">Fant ikke golfbanen...</div>; return <div className="p-20 text-center font-bold text-2xl">Fant ikke golfbanen...</div>;
} }
// Vi sender dataene til den navngitte komponenten const facilityJsonLd = createFacilityJsonLd(facility);
return <FacilityDetailView facility={facility} />; const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "Golfbaner", path: "/golfbaner" },
{ name: facility.name, path: `/golfbaner/${facility.slug}` },
]);
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(facilityJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<FacilityDetailView facility={facility} />
</>
);
} }

View file

@ -1,8 +1,23 @@
import FacilitySearch from "@/app/FacilitySearch"; import FacilitySearch from "@/app/FacilitySearch";
import { API_URL } from "@/config/constants"; import { API_URL } from "@/config/constants";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
const pageTitle = "Alle golfbaner i Norge";
const pageDescription =
"Filtrer norske golfbaner etter område, banestatus, antall hull og fasiliteter i TeeOffs samlede oversikt.";
export const metadata = createPageMetadata({
title: pageTitle,
description: pageDescription,
path: "/golfbaner",
});
export default async function GolfCoursesIndexPage() { export default async function GolfCoursesIndexPage() {
let facilities = []; let facilities = [];
@ -23,8 +38,26 @@ export default async function GolfCoursesIndexPage() {
} }
const safeData = Array.isArray(facilities) ? facilities : []; const safeData = Array.isArray(facilities) ? facilities : [];
const collectionJsonLd = createCollectionPageJsonLd({
name: pageTitle,
description: pageDescription,
path: "/golfbaner",
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "Golfbaner", path: "/golfbaner" },
]);
return ( return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<main className="site-shell min-h-screen"> <main className="site-shell min-h-screen">
<FacilitySearch <FacilitySearch
initialFacilities={safeData} initialFacilities={safeData}
@ -34,5 +67,6 @@ export default async function GolfCoursesIndexPage() {
intro="Bruk område, banestatus og fasiliteter for å snevre inn oversikten. Her får katalogen være arbeidsflate, ikke hero." intro="Bruk område, banestatus og fasiliteter for å snevre inn oversikten. Her får katalogen være arbeidsflate, ikke hero."
/> />
</main> </main>
</>
); );
} }

View file

@ -2,6 +2,13 @@ import type { Metadata } from "next";
import { Mulish, Oswald } from "next/font/google"; import { Mulish, Oswald } from "next/font/google";
import "./globals.css"; import "./globals.css";
import Header from "@/components/Header"; import Header from "@/components/Header";
import {
DEFAULT_DESCRIPTION,
DEFAULT_OG_IMAGE,
SITE_URL,
createOrganizationJsonLd,
createWebsiteJsonLd,
} from "@/app/seo";
const uiFont = Mulish({ const uiFont = Mulish({
subsets: ["latin"], subsets: ["latin"],
@ -16,8 +23,51 @@ const displayFont = Oswald({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: {
default: "TeeOff.no - Din guide til norske golfbaner",
template: "%s | TeeOff.no",
},
description: DEFAULT_DESCRIPTION,
applicationName: "TeeOff",
alternates: {
canonical: "/",
},
openGraph: {
type: "website",
locale: "nb_NO",
siteName: "TeeOff",
title: "TeeOff.no - Din guide til norske golfbaner", title: "TeeOff.no - Din guide til norske golfbaner",
description: "Oppdatert banestatus, priser og informasjon om alle norske golfanlegg.", description: DEFAULT_DESCRIPTION,
url: SITE_URL,
images: [
{
url: DEFAULT_OG_IMAGE,
width: 1600,
height: 900,
alt: "TeeOff.no",
},
],
},
twitter: {
card: "summary_large_image",
site: "@TeeOffno",
creator: "@TeeOffno",
title: "TeeOff.no - Din guide til norske golfbaner",
description: DEFAULT_DESCRIPTION,
images: [DEFAULT_OG_IMAGE],
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
maxSnippet: -1,
maxImagePreview: "large",
maxVideoPreview: -1,
},
},
icons: { icons: {
icon: "/icons/cropped-siteicon-1.png", icon: "/icons/cropped-siteicon-1.png",
shortcut: "/icons/cropped-siteicon-1.png", shortcut: "/icons/cropped-siteicon-1.png",
@ -26,9 +76,20 @@ export const metadata: Metadata = {
}; };
export default function RootLayout({ children }: { children: React.ReactNode }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
const organizationJsonLd = createOrganizationJsonLd();
const websiteJsonLd = createWebsiteJsonLd();
return ( return (
<html lang="nb"> <html lang="nb">
<body className={`${uiFont.variable} ${displayFont.variable} antialiased`}> <body className={`${uiFont.variable} ${displayFont.variable} antialiased`}>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteJsonLd) }}
/>
<Header /> <Header />
{children} {children}
</body> </body>

View file

@ -1,8 +1,23 @@
import { API_URL } from "@/config/constants"; import { API_URL } from "@/config/constants";
import MembershipExplorer, { type MembershipFacility } from "./MembershipExplorer"; import MembershipExplorer, { type MembershipFacility } from "./MembershipExplorer";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
const pageTitle = "Medlemskap i norske golfklubber";
const pageDescription =
"Sammenlign priser på medlemskap i norske golfklubber, både full spillerett og rimeligste nasjonale alternativ.";
export const metadata = createPageMetadata({
title: pageTitle,
description: pageDescription,
path: "/medlemskap",
});
export default async function MembershipPage() { export default async function MembershipPage() {
let facilities: MembershipFacility[] = []; let facilities: MembershipFacility[] = [];
@ -28,8 +43,26 @@ export default async function MembershipPage() {
typeof facility.standard_medlemskap === "number" || typeof facility.standard_medlemskap === "number" ||
typeof facility.rimeligste_alternativ === "number", typeof facility.rimeligste_alternativ === "number",
); );
const collectionJsonLd = createCollectionPageJsonLd({
name: pageTitle,
description: pageDescription,
path: "/medlemskap",
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "Medlemskap", path: "/medlemskap" },
]);
return ( return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<main className="site-shell min-h-screen"> <main className="site-shell min-h-screen">
<section className="border-b border-[#112015]/8 bg-[linear-gradient(135deg,rgba(139,195,74,0.16),rgba(255,255,255,0.92))]"> <section className="border-b border-[#112015]/8 bg-[linear-gradient(135deg,rgba(139,195,74,0.16),rgba(255,255,255,0.92))]">
<div className="mx-auto max-w-[1400px] px-4 py-14 sm:px-6 lg:px-8 lg:py-20"> <div className="mx-auto max-w-[1400px] px-4 py-14 sm:px-6 lg:px-8 lg:py-20">
@ -57,5 +90,6 @@ export default async function MembershipPage() {
<MembershipExplorer facilities={visibleFacilities} /> <MembershipExplorer facilities={visibleFacilities} />
</section> </section>
</main> </main>
</>
); );
} }

View file

@ -1,8 +1,15 @@
import FacilitySearch from "./FacilitySearch"; import FacilitySearch from "./FacilitySearch";
import HeroSlider from "./HeroSlider"; import HeroSlider from "./HeroSlider";
import { API_URL } from "@/config/constants"; import { API_URL } from "@/config/constants";
import { createPageMetadata } from "@/app/seo";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export const metadata = createPageMetadata({
title: "Din guide til norske golfbaner",
description:
"Utforsk norske golfanlegg med oppdatert banestatus, kart, priser, medlemskap og Veien til Golf samlet på TeeOff.",
path: "/",
});
export default async function Home() { export default async function Home() {
let facilities = []; let facilities = [];

16
frontend/src/app/robots.ts Executable file
View file

@ -0,0 +1,16 @@
import type { MetadataRoute } from "next";
import { SITE_URL } from "@/app/seo";
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: ["/admin"],
},
],
sitemap: `${SITE_URL}/sitemap.xml`,
host: SITE_URL,
};
}

223
frontend/src/app/seo.ts Executable file
View file

@ -0,0 +1,223 @@
import type { Metadata } from "next";
import { FALLBACK_IMAGE } from "@/config/constants";
import type { FacilityRecord } from "@/app/facilityData";
type MetadataInput = {
title: string;
description: string;
path?: string;
image?: string | null;
type?: "website" | "article";
};
type BreadcrumbItem = {
name: string;
path: string;
};
type CollectionPageInput = {
name: string;
description: string;
path: string;
};
type SocialLink = {
url?: string | null;
};
type FacilitySeoRecord = FacilityRecord & {
address?: string | null;
email?: string | null;
social_links?: unknown;
};
export const SITE_NAME = "TeeOff";
export const SITE_URL = (process.env.NEXT_PUBLIC_SITE_URL || "https://nye.teeoff.no").replace(/\/$/, "");
export const DEFAULT_DESCRIPTION =
"Oppdatert banestatus, priser, Veien til Golf og informasjon om norske golfanlegg samlet på ett sted.";
export const DEFAULT_OG_IMAGE = buildAbsoluteUrl(FALLBACK_IMAGE);
export const ORGANIZATION_ID = `${SITE_URL}#organization`;
export const WEBSITE_ID = `${SITE_URL}#website`;
const SAME_AS_LINKS = [
"https://www.facebook.com/TeeOff.norge/",
"https://twitter.com/TeeOffno",
"https://www.instagram.com/teeoffno/",
];
export function buildAbsoluteUrl(path = "/") {
if (!path) return SITE_URL;
if (/^https?:\/\//i.test(path)) return path;
return `${SITE_URL}${path.startsWith("/") ? path : `/${path}`}`;
}
export function resolveImageUrl(image?: string | null) {
if (!image) return DEFAULT_OG_IMAGE;
return buildAbsoluteUrl(image);
}
export function stripHtml(value: string | null | undefined) {
return String(value || "")
.replace(/<[^>]+>/g, " ")
.replace(/&nbsp;/gi, " ")
.replace(/\s+/g, " ")
.trim();
}
export function trimDescription(value: string | null | undefined, maxLength = 160) {
const plain = stripHtml(value);
if (plain.length <= maxLength) return plain;
return `${plain.slice(0, maxLength - 1).trimEnd()}`;
}
export function createPageMetadata({
title,
description,
path = "/",
image,
type = "website",
}: MetadataInput): Metadata {
const ogImage = resolveImageUrl(image);
return {
title,
description,
alternates: {
canonical: path,
},
openGraph: {
type,
locale: "nb_NO",
siteName: SITE_NAME,
title,
description,
url: buildAbsoluteUrl(path),
images: [
{
url: ogImage,
width: 1600,
height: 900,
alt: title,
},
],
},
twitter: {
card: "summary_large_image",
site: "@TeeOffno",
creator: "@TeeOffno",
title,
description,
images: [ogImage],
},
};
}
export function createOrganizationJsonLd() {
return {
"@context": "https://schema.org",
"@type": "Organization",
"@id": ORGANIZATION_ID,
name: SITE_NAME,
url: SITE_URL,
logo: {
"@type": "ImageObject",
url: buildAbsoluteUrl("/icons/cropped-siteicon-1.png"),
},
sameAs: SAME_AS_LINKS,
};
}
export function createWebsiteJsonLd() {
return {
"@context": "https://schema.org",
"@type": "WebSite",
"@id": WEBSITE_ID,
url: SITE_URL,
name: SITE_NAME,
inLanguage: "nb-NO",
publisher: {
"@id": ORGANIZATION_ID,
},
};
}
export function createBreadcrumbJsonLd(items: BreadcrumbItem[]) {
return {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: items.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
name: item.name,
item: buildAbsoluteUrl(item.path),
})),
};
}
export function createCollectionPageJsonLd({ name, description, path }: CollectionPageInput) {
return {
"@context": "https://schema.org",
"@type": "CollectionPage",
name,
description,
url: buildAbsoluteUrl(path),
inLanguage: "nb-NO",
isPartOf: {
"@id": WEBSITE_ID,
},
about: {
"@id": ORGANIZATION_ID,
},
};
}
export function createFacilityJsonLd(facility: FacilitySeoRecord) {
const socialLinks = parseJson<SocialLink[]>(facility.social_links, []);
const sameAs = [facility.website_url, ...socialLinks.map((entry) => entry?.url || null)].filter(Boolean);
return {
"@context": "https://schema.org",
"@type": "GolfCourse",
"@id": buildAbsoluteUrl(`/golfbaner/${facility.slug}#golfcourse`),
name: facility.name,
description:
trimDescription(facility.description) ||
`${facility.name} er et golfanlegg på TeeOff med oppdatert banestatus og praktisk klubbinfo.`,
url: buildAbsoluteUrl(`/golfbaner/${facility.slug}`),
image: resolveImageUrl(facility.image_url),
telephone: facility.phone || undefined,
email: facility.email || undefined,
address:
facility.address || facility.city || facility.county
? {
"@type": "PostalAddress",
streetAddress: facility.address || undefined,
addressLocality: facility.city || undefined,
addressRegion: facility.county || undefined,
addressCountry: "NO",
}
: undefined,
geo:
typeof facility.lat === "number" && typeof facility.lng === "number"
? {
"@type": "GeoCoordinates",
latitude: facility.lat,
longitude: facility.lng,
}
: undefined,
sameAs: sameAs.length > 0 ? sameAs : undefined,
isPartOf: {
"@id": WEBSITE_ID,
},
};
}
function parseJson<T>(value: unknown, fallback: T): T {
if (!value) return fallback;
if (typeof value === "object") return value as T;
try {
return JSON.parse(String(value)) as T;
} catch {
return fallback;
}
}

69
frontend/src/app/sitemap.ts Executable file
View file

@ -0,0 +1,69 @@
import type { MetadataRoute } from "next";
import { API_URL } from "@/config/constants";
import { getAvailablePlaceConfigs } from "@/app/facilityData";
import { buildAbsoluteUrl } from "@/app/seo";
type SitemapFacility = {
slug?: string;
status_updated_at?: string | null;
vtg_updated_at?: string | null;
};
const staticRoutes: MetadataRoute.Sitemap = [
{
url: buildAbsoluteUrl("/"),
lastModified: new Date(),
changeFrequency: "daily",
priority: 1,
},
{
url: buildAbsoluteUrl("/golfbaner"),
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.95,
},
{
url: buildAbsoluteUrl("/medlemskap"),
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.8,
},
{
url: buildAbsoluteUrl("/vtg"),
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.8,
},
];
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
let facilities: SitemapFacility[] = [];
try {
const res = await fetch(`${API_URL}/facilities`, { cache: "no-store" });
if (res.ok) {
const data = await res.json();
facilities = Array.isArray(data) ? data : [];
}
} catch {
facilities = [];
}
const placeRoutes = getAvailablePlaceConfigs().map((slug) => ({
url: buildAbsoluteUrl(`/sted/${slug}`),
lastModified: new Date(),
changeFrequency: "daily" as const,
priority: slug === "norge" ? 0.9 : 0.75,
}));
const facilityRoutes = facilities
.filter((facility) => Boolean(facility.slug))
.map((facility) => ({
url: buildAbsoluteUrl(`/golfbaner/${facility.slug}`),
lastModified: facility.status_updated_at || facility.vtg_updated_at || new Date(),
changeFrequency: "daily" as const,
priority: 0.7,
}));
return [...staticRoutes, ...placeRoutes, ...facilityRoutes];
}

View file

@ -1,3 +1,4 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import FacilitySearch from "@/app/FacilitySearch"; import FacilitySearch from "@/app/FacilitySearch";
import PlaceMap from "@/components/PlaceMap"; import PlaceMap from "@/components/PlaceMap";
@ -9,6 +10,11 @@ import {
getPlaceConfigFromSlug, getPlaceConfigFromSlug,
} from "@/app/facilityData"; } from "@/app/facilityData";
import { API_URL } from "@/config/constants"; import { API_URL } from "@/config/constants";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
export const dynamicParams = true; export const dynamicParams = true;
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@ -17,6 +23,29 @@ export async function generateStaticParams() {
return getAvailablePlaceConfigs().map((slug) => ({ slug })); return getAvailablePlaceConfigs().map((slug) => ({ slug }));
} }
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const place = getPlaceConfigFromSlug(slug);
if (!place) {
return createPageMetadata({
title: "Sted ikke funnet",
description: "Denne stedssiden finnes ikke på TeeOff.",
path: `/sted/${slug}`,
});
}
return createPageMetadata({
title: place.title,
description: place.intro,
path: `/sted/${slug}`,
});
}
export default async function PlacePage({ params }: { params: Promise<{ slug: string }> }) { export default async function PlacePage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params; const { slug } = await params;
const place = getPlaceConfigFromSlug(slug); const place = getPlaceConfigFromSlug(slug);
@ -45,8 +74,27 @@ export default async function PlacePage({ params }: { params: Promise<{ slug: st
const safeData = Array.isArray(facilities) ? facilities : []; const safeData = Array.isArray(facilities) ? facilities : [];
const facilitiesInPlace = filterFacilitiesByArea(enrichFacilities(safeData), place.areaFilter); const facilitiesInPlace = filterFacilitiesByArea(enrichFacilities(safeData), place.areaFilter);
const collectionJsonLd = createCollectionPageJsonLd({
name: place.title,
description: place.intro,
path: `/sted/${slug}`,
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "Steder", path: "/sted/norge" },
{ name: place.label, path: `/sted/${slug}` },
]);
return ( return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<main className="site-shell min-h-screen"> <main className="site-shell min-h-screen">
<section className="mx-auto max-w-[1400px] px-4 py-8 sm:px-6 sm:py-10 lg:px-8 lg:py-12"> <section className="mx-auto max-w-[1400px] px-4 py-8 sm:px-6 sm:py-10 lg:px-8 lg:py-12">
<div className="max-w-4xl"> <div className="max-w-4xl">
@ -77,5 +125,6 @@ export default async function PlacePage({ params }: { params: Promise<{ slug: st
hideTitleBlock hideTitleBlock
/> />
</main> </main>
</>
); );
} }

View file

@ -1,9 +1,24 @@
import { API_URL } from "@/config/constants"; import { API_URL } from "@/config/constants";
import VtgExplorer from "./VtgExplorer"; import VtgExplorer from "./VtgExplorer";
import type { FacilityRecord } from "@/app/facilityData"; import type { FacilityRecord } from "@/app/facilityData";
import {
createBreadcrumbJsonLd,
createCollectionPageJsonLd,
createPageMetadata,
} from "@/app/seo";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
const pageTitle = "Veien til Golf";
const pageDescription =
"Finn Veien til Golf-kurs etter område, klubb, pris og neste kursdato i TeeOffs VTG-oversikt.";
export const metadata = createPageMetadata({
title: pageTitle,
description: pageDescription,
path: "/vtg",
});
export default async function VtgPage() { export default async function VtgPage() {
let facilities: FacilityRecord[] = []; let facilities: FacilityRecord[] = [];
@ -24,9 +39,29 @@ export default async function VtgPage() {
facilities = []; facilities = [];
} }
const collectionJsonLd = createCollectionPageJsonLd({
name: pageTitle,
description: pageDescription,
path: "/vtg",
});
const breadcrumbJsonLd = createBreadcrumbJsonLd([
{ name: "Hjem", path: "/" },
{ name: "VTG", path: "/vtg" },
]);
return ( return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionJsonLd) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
<main className="site-shell min-h-screen"> <main className="site-shell min-h-screen">
<VtgExplorer facilities={facilities} /> <VtgExplorer facilities={facilities} />
</main> </main>
</>
); );
} }