Skip to content

Commit 3f275c0

Browse files
committed
Add SEO, sitemap/robots and bump image to v1.1.0
Add site SEO utilities and metadata and wire them into the app. Introduce docs/src/lib/seo.ts and apply Metadata to layout, home and docs pages (openGraph, twitter, canonical/alternates, robots). Add robots.ts and sitemap.ts routes (sitemap built from source pages). Also update quick-start/installation examples and homepage: hhftechnology/crowdsec-manager image bumped from 1.0.0 to 1.1.0 and the displayed stable release updated to 1.1.0.
1 parent 1e3d343 commit 3f275c0

8 files changed

Lines changed: 177 additions & 5 deletions

File tree

docs/content/docs/installation.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Use this minimum compose file:
2727
```yaml
2828
services:
2929
crowdsec-manager:
30-
image: hhftechnology/crowdsec-manager:1.0.0
30+
image: hhftechnology/crowdsec-manager:1.1.0
3131
container_name: crowdsec-manager
3232
restart: unless-stopped
3333
expose:

docs/content/docs/quick-start.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ mkdir -p ./backups ./data
1818
```yaml
1919
services:
2020
crowdsec-manager:
21-
image: hhftechnology/crowdsec-manager:1.0.0
21+
image: hhftechnology/crowdsec-manager:1.1.0
2222
container_name: crowdsec-manager
2323
restart: unless-stopped
2424
expose:

docs/src/app/(home)/page.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
11
import Link from 'next/link';
2+
import type { Metadata } from 'next';
3+
import { getSiteUrl } from '@/lib/seo';
4+
5+
const siteUrl = getSiteUrl();
6+
7+
export const metadata: Metadata = {
8+
title: 'Home',
9+
description:
10+
'CrowdSec Manager documentation home. Start with installation, quick start, features, and API references.',
11+
alternates: {
12+
canonical: '/',
13+
},
14+
openGraph: {
15+
type: 'website',
16+
url: siteUrl,
17+
title: 'CrowdSec Manager Documentation',
18+
description:
19+
'Start here for CrowdSec Manager setup, configuration, and API documentation.',
20+
},
21+
twitter: {
22+
card: 'summary_large_image',
23+
title: 'CrowdSec Manager Documentation',
24+
description:
25+
'Start here for CrowdSec Manager setup, configuration, and API documentation.',
26+
},
27+
};
228

329
const quickInstall = `services:
430
crowdsec-manager:
5-
image: hhftechnology/crowdsec-manager:1.0.0
31+
image: hhftechnology/crowdsec-manager:1.1.0
632
container_name: crowdsec-manager
733
restart: unless-stopped
834
expose:
@@ -35,7 +61,7 @@ export default function HomePage() {
3561
Stable Release
3662
</p>
3763
<h1 className="mb-4 text-4xl font-extrabold tracking-tight text-foreground lg:text-6xl">
38-
CrowdSec Manager 1.0.0
64+
CrowdSec Manager 1.1.0
3965
</h1>
4066
<p className="mx-auto mb-8 max-w-3xl text-base text-muted-foreground lg:text-lg">
4167
Manage CrowdSec, Traefik integration, decisions, scenarios, logs, backups, and updates

docs/src/app/docs/[[...slug]]/page.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { notFound } from 'next/navigation';
99
import { getMDXComponents } from '@/mdx-components';
1010
import type { Metadata } from 'next';
1111
import { createRelativeLink } from 'fumadocs-ui/mdx';
12+
import { getSiteUrl, siteMetadata } from '@/lib/seo';
1213

1314
export default async function Page(props: PageProps<'/docs/[[...slug]]'>) {
1415
const params = await props.params;
@@ -43,12 +44,41 @@ export async function generateMetadata(
4344
const params = await props.params;
4445
const page = source.getPage(params.slug);
4546
if (!page) notFound();
47+
const siteUrl = getSiteUrl();
48+
const pathname = page.slugs.length > 0 ? `/docs/${page.slugs.join('/')}` : '/docs';
49+
const canonicalUrl = `${siteUrl}${pathname}`;
50+
const ogImageUrl = `${siteUrl}${getPageImage(page).url}`;
4651

4752
return {
4853
title: page.data.title,
4954
description: page.data.description,
55+
alternates: {
56+
canonical: pathname,
57+
},
58+
robots: {
59+
index: true,
60+
follow: true,
61+
},
5062
openGraph: {
51-
images: getPageImage(page).url,
63+
type: 'article',
64+
url: canonicalUrl,
65+
siteName: siteMetadata.name,
66+
title: page.data.title,
67+
description: page.data.description,
68+
images: [
69+
{
70+
url: ogImageUrl,
71+
width: 1200,
72+
height: 630,
73+
alt: page.data.title,
74+
},
75+
],
76+
},
77+
twitter: {
78+
card: 'summary_large_image',
79+
title: page.data.title,
80+
description: page.data.description,
81+
images: [ogImageUrl],
5282
},
5383
};
5484
}

docs/src/app/layout.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,55 @@
11
import { RootProvider } from 'fumadocs-ui/provider/next';
22
import './global.css';
33
import { Inter } from 'next/font/google';
4+
import type { Metadata } from 'next';
5+
import { getSiteUrl, siteMetadata } from '@/lib/seo';
46

57
const inter = Inter({
68
subsets: ['latin'],
79
});
810

911
import { type ReactNode } from 'react';
1012

13+
const siteUrl = getSiteUrl();
14+
15+
export const metadata: Metadata = {
16+
metadataBase: new URL(siteUrl),
17+
title: {
18+
default: siteMetadata.title,
19+
template: `%s | ${siteMetadata.name}`,
20+
},
21+
description: siteMetadata.description,
22+
keywords: siteMetadata.keywords,
23+
applicationName: siteMetadata.name,
24+
alternates: {
25+
canonical: '/',
26+
},
27+
robots: {
28+
index: true,
29+
follow: true,
30+
googleBot: {
31+
index: true,
32+
follow: true,
33+
'max-image-preview': 'large',
34+
'max-snippet': -1,
35+
'max-video-preview': -1,
36+
},
37+
},
38+
openGraph: {
39+
type: 'website',
40+
locale: 'en_US',
41+
url: siteUrl,
42+
siteName: siteMetadata.name,
43+
title: siteMetadata.title,
44+
description: siteMetadata.description,
45+
},
46+
twitter: {
47+
card: 'summary_large_image',
48+
title: siteMetadata.title,
49+
description: siteMetadata.description,
50+
},
51+
};
52+
1153
export default function Layout({ children }: { children: ReactNode }) {
1254
return (
1355
<html lang="en" className={inter.className} suppressHydrationWarning>

docs/src/app/robots.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { MetadataRoute } from 'next';
2+
import { getSiteUrl } from '@/lib/seo';
3+
4+
export default function robots(): MetadataRoute.Robots {
5+
const siteUrl = getSiteUrl();
6+
7+
return {
8+
rules: {
9+
userAgent: '*',
10+
allow: '/',
11+
},
12+
sitemap: `${siteUrl}/sitemap.xml`,
13+
host: siteUrl,
14+
};
15+
}

docs/src/app/sitemap.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { MetadataRoute } from 'next';
2+
import { source } from '@/lib/source';
3+
import { getSiteUrl } from '@/lib/seo';
4+
5+
export default function sitemap(): MetadataRoute.Sitemap {
6+
const siteUrl = getSiteUrl();
7+
const now = new Date();
8+
9+
const staticRoutes: MetadataRoute.Sitemap = [
10+
{
11+
url: siteUrl,
12+
lastModified: now,
13+
changeFrequency: 'weekly',
14+
priority: 1,
15+
},
16+
{
17+
url: `${siteUrl}/docs`,
18+
lastModified: now,
19+
changeFrequency: 'daily',
20+
priority: 0.9,
21+
},
22+
];
23+
24+
const docRoutes: MetadataRoute.Sitemap = source
25+
.getPages()
26+
.filter((page) => page.slugs.length > 0)
27+
.map((page) => ({
28+
url: `${siteUrl}/docs/${page.slugs.join('/')}`,
29+
lastModified: now,
30+
changeFrequency: 'weekly',
31+
priority: page.slugs.length === 1 ? 0.85 : 0.75,
32+
}));
33+
34+
return [...staticRoutes, ...docRoutes];
35+
}

docs/src/lib/seo.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const fallbackSiteUrl = 'https://crowdsec-manager-docs.vercel.app';
2+
3+
export function getSiteUrl(): string {
4+
const envUrl = process.env.NEXT_PUBLIC_SITE_URL ?? process.env.SITE_URL;
5+
if (!envUrl) return fallbackSiteUrl;
6+
7+
const withProtocol = envUrl.startsWith('http') ? envUrl : `https://${envUrl}`;
8+
return withProtocol.replace(/\/+$/, '');
9+
}
10+
11+
export const siteMetadata = {
12+
name: 'CrowdSec Manager Docs',
13+
title: 'CrowdSec Manager Documentation',
14+
description:
15+
'Official documentation for CrowdSec Manager: installation, configuration, API, and operational guides.',
16+
keywords: [
17+
'CrowdSec Manager',
18+
'CrowdSec',
19+
'Pangolin',
20+
'Traefik',
21+
'security dashboard',
22+
'CrowdSec documentation',
23+
],
24+
};

0 commit comments

Comments
 (0)