Openstatus www.openstatus.dev

refactor: report templates to guides (#1811)

* refactor: report-templates to guides

* fix: cmdk search

authored by

Maximilian Kaske and committed by
GitHub
8ea946d1 e463f837

+94 -87
+37
apps/web/src/app/(landing)/guides/page.tsx
··· 1 + import { defaultMetadata, ogMetadata } from "@/app/shared-metadata"; 2 + import { twitterMetadata } from "@/app/shared-metadata"; 3 + import { getGuides } from "@/content/utils"; 4 + import type { Metadata } from "next"; 5 + import { ContentCategory } from "../content-category"; 6 + import { ContentList } from "../content-list"; 7 + 8 + const TITLE = "Guides"; 9 + const DESCRIPTION = "All the latest guides from openstatus."; 10 + 11 + export const metadata: Metadata = { 12 + ...defaultMetadata, 13 + title: TITLE, 14 + description: DESCRIPTION, 15 + openGraph: { 16 + ...ogMetadata, 17 + title: TITLE, 18 + description: DESCRIPTION, 19 + }, 20 + twitter: { 21 + ...twitterMetadata, 22 + title: TITLE, 23 + description: DESCRIPTION, 24 + images: [`/api/og?title=${TITLE}&description=${DESCRIPTION}`], 25 + }, 26 + }; 27 + 28 + export default function GuidesListPage() { 29 + const allGuides = getGuides(); 30 + return ( 31 + <div className="prose dark:prose-invert max-w-none"> 32 + <h1>Guides</h1> 33 + <ContentCategory data={allGuides} prefix="/guides" /> 34 + <ContentList data={allGuides} prefix="/guides" withCategory /> 35 + </div> 36 + ); 37 + }
+10 -10
apps/web/src/app/(landing)/report-template/[slug]/page.tsx apps/web/src/app/(landing)/guides/[slug]/page.tsx
··· 5 5 getPageMetadata, 6 6 } from "@/app/shared-metadata"; 7 7 import { CustomMDX } from "@/content/mdx"; 8 - import { formatDate, getReportTemplates } from "@/content/utils"; 8 + import { formatDate, getGuides } from "@/content/utils"; 9 9 import { getAuthor } from "@/data/author"; 10 10 import type { Metadata } from "next"; 11 11 import Image from "next/image"; ··· 16 16 export const dynamicParams = false; 17 17 18 18 export async function generateStaticParams() { 19 - const posts = getReportTemplates(); 19 + const posts = getGuides(); 20 20 21 21 return posts.map((post) => ({ 22 22 slug: post.slug, ··· 29 29 params: Promise<{ slug: string }>; 30 30 }): Promise<Metadata | undefined> { 31 31 const { slug } = await params; 32 - const post = getReportTemplates().find((post) => post.slug === slug); 32 + const post = getGuides().find((post) => post.slug === slug); 33 33 if (!post) { 34 34 return; 35 35 } 36 36 37 - const metadata = getPageMetadata(post, "report-template"); 37 + const metadata = getPageMetadata(post, "guides"); 38 38 39 39 return metadata; 40 40 } 41 41 42 - export default async function ReportTemplate({ 42 + export default async function Guide({ 43 43 params, 44 44 }: { 45 45 params: Promise<{ slug: string }>; 46 46 }) { 47 47 const { slug } = await params; 48 - const posts = getReportTemplates().sort( 48 + const posts = getGuides().sort( 49 49 (a, b) => 50 50 b.metadata.publishedAt.getTime() - a.metadata.publishedAt.getTime(), 51 51 ); ··· 60 60 61 61 const jsonLDBlog: WithContext<BlogPosting> = getJsonLDBlogPosting( 62 62 post, 63 - "report-template", 63 + "guides", 64 64 ); 65 65 66 66 const jsonLDBreadcrumb: WithContext<BreadcrumbList> = getJsonLDBreadcrumbList( 67 67 [ 68 68 { name: "Home", url: BASE_URL }, 69 - { name: "Report Templates", url: `${BASE_URL}/report-template` }, 70 - { name: post.metadata.title, url: `${BASE_URL}/report-template/${slug}` }, 69 + { name: "Guides", url: `${BASE_URL}/guides` }, 70 + { name: post.metadata.title, url: `${BASE_URL}/guides/${slug}` }, 71 71 ], 72 72 ); 73 73 ··· 108 108 <ContentPagination 109 109 previousPost={previousPost} 110 110 nextPost={nextPost} 111 - prefix="/report-template" 111 + prefix="/guides" 112 112 /> 113 113 </section> 114 114 );
+10 -10
apps/web/src/app/(landing)/report-template/category/[slug]/page.tsx apps/web/src/app/(landing)/guides/category/[slug]/page.tsx
··· 5 5 ogMetadata, 6 6 twitterMetadata, 7 7 } from "@/app/shared-metadata"; 8 - import { getReportTemplates } from "@/content/utils"; 8 + import { getGuides } from "@/content/utils"; 9 9 import type { Metadata } from "next"; 10 10 11 - const TITLE = "Report Template Category"; 12 - const DESCRIPTION = "All the latest templates from openstatus."; 11 + const TITLE = "Guide Category"; 12 + const DESCRIPTION = "All the latest guides from openstatus."; 13 13 14 14 export const metadata: Metadata = { 15 15 ...defaultMetadata, ··· 31 31 export const dynamicParams = false; 32 32 33 33 export async function generateStaticParams() { 34 - const posts = getReportTemplates(); 34 + const posts = getGuides(); 35 35 const categories = [...new Set(posts.map((post) => post.metadata.category))]; 36 36 37 37 return categories.map((category) => ({ ··· 39 39 })); 40 40 } 41 41 42 - export default async function ReportTemplateCategoryPage({ 42 + export default async function GuideCategoryPage({ 43 43 params, 44 44 }: { 45 45 params: Promise<{ slug: string }>; 46 46 }) { 47 47 const { slug } = await params; 48 - const allReportTemplates = getReportTemplates(); 49 - const filteredReportTemplates = allReportTemplates.filter( 48 + const allGuides = getGuides(); 49 + const filteredGuides = allGuides.filter( 50 50 (post) => post.metadata.category.toLowerCase() === slug.toLowerCase(), 51 51 ); 52 52 53 53 return ( 54 54 <div className="prose dark:prose-invert max-w-none"> 55 - <h1 className="capitalize">Report Template | {slug}</h1> 56 - <ContentCategory data={allReportTemplates} prefix="/report-template" /> 57 - <ContentList data={filteredReportTemplates} prefix="/report-template" /> 55 + <h1 className="capitalize">Guides | {slug}</h1> 56 + <ContentCategory data={allGuides} prefix="/guides" /> 57 + <ContentList data={filteredGuides} prefix="/guides" /> 58 58 </div> 59 59 ); 60 60 }
-41
apps/web/src/app/(landing)/report-template/page.tsx
··· 1 - import { defaultMetadata, ogMetadata } from "@/app/shared-metadata"; 2 - import { twitterMetadata } from "@/app/shared-metadata"; 3 - import { getReportTemplates } from "@/content/utils"; 4 - import type { Metadata } from "next"; 5 - import { ContentCategory } from "../content-category"; 6 - import { ContentList } from "../content-list"; 7 - 8 - const TITLE = "Report Template"; 9 - const DESCRIPTION = "All the latest templates from openstatus."; 10 - 11 - export const metadata: Metadata = { 12 - ...defaultMetadata, 13 - title: TITLE, 14 - description: DESCRIPTION, 15 - openGraph: { 16 - ...ogMetadata, 17 - title: TITLE, 18 - description: DESCRIPTION, 19 - }, 20 - twitter: { 21 - ...twitterMetadata, 22 - title: TITLE, 23 - description: DESCRIPTION, 24 - images: [`/api/og?title=${TITLE}&description=${DESCRIPTION}`], 25 - }, 26 - }; 27 - 28 - export default function ReportTemplateListPage() { 29 - const allReportTemplates = getReportTemplates(); 30 - return ( 31 - <div className="prose dark:prose-invert max-w-none"> 32 - <h1>Report Templates</h1> 33 - <ContentCategory data={allReportTemplates} prefix="/report-template" /> 34 - <ContentList 35 - data={allReportTemplates} 36 - prefix="/report-template" 37 - withCategory 38 - /> 39 - </div> 40 - ); 41 - }
+1
apps/web/src/app/api/search/route.ts
··· 70 70 ...getPages("tools").filter((tool) => tool.slug !== "checker-slug"), 71 71 ...getPages("compare"), 72 72 ...getPages("product"), 73 + ...getPages("guides"), 73 74 home, 74 75 ]; 75 76 } else {
+6 -6
apps/web/src/app/sitemap.ts
··· 2 2 getBlogPosts, 3 3 getChangelogPosts, 4 4 getComparePages, 5 + getGuides, 5 6 getHomePage, 6 7 getProductPages, 7 - getReportTemplates, 8 8 getToolsPages, 9 9 getUnrelatedPages, 10 10 } from "@/content/utils"; ··· 20 20 const allPlaygrounds = getToolsPages().filter( 21 21 (tool) => tool.slug !== "checker-slug", 22 22 ); 23 - const allReportTemplates = getReportTemplates(); 23 + const allGuides = getGuides(); 24 24 25 25 export default function sitemap(): MetadataRoute.Sitemap { 26 26 const blogs = allPosts.map((post) => ({ ··· 65 65 priority: 0.6, 66 66 })); 67 67 68 - const reportTemplates = allReportTemplates.map((reportTemplate) => ({ 69 - url: `https://www.openstatus.dev/report-template/${reportTemplate.slug}`, 70 - lastModified: reportTemplate.metadata.publishedAt, 68 + const guides = allGuides.map((guide) => ({ 69 + url: `https://www.openstatus.dev/guides/${guide.slug}`, 70 + lastModified: guide.metadata.publishedAt, 71 71 changeFrequency: "monthly" as const, 72 72 priority: 0.6, 73 73 })); ··· 89 89 ...landings, 90 90 ...products, 91 91 ...playgrounds, 92 - ...reportTemplates, 92 + ...guides, 93 93 ]; 94 94 }
+6
apps/web/src/content/cmdk.tsx
··· 107 107 page: "compare", 108 108 }, 109 109 { 110 + type: "group", 111 + label: "Search in Guides...", 112 + heading: "Guides", 113 + page: "guides", 114 + }, 115 + { 110 116 type: "item", 111 117 label: "Go to About", 112 118 href: "/about",
+1 -1
apps/web/src/content/pages/blog/top-five-atlassian-statuspage-alternatives.mdx apps/web/src/content/pages/guides/top-five-atlassian-statuspage-alternatives.mdx
··· 4 4 description: "Explore the best alternatives to Atlassian Statuspage including OpenStatus, Status-io, Datadog Status Page, Instatus, and Betterstack." 5 5 author: "Thibault Le Ouay Ducasse" 6 6 publishedAt: "2025-11-05" 7 - category: "guide" 7 + category: "alternative" 8 8 --- 9 9 10 10 Atlassian Statuspage has long been a go-to choice for organizations needing to communicate their service status. It's a robust, mature platform. However, the service reliability and incident communication landscape is evolving rapidly.
+2 -2
apps/web/src/content/pages/report-template/api-service-disruption.mdx apps/web/src/content/pages/guides/api-service-disruption.mdx
··· 1 1 --- 2 2 title: "API Service Disruption Template" 3 3 description: "Professional template for communicating API outages and third-party service failures to your users." 4 - author: "OpenStatus" 4 + author: "openstatus" 5 5 publishedAt: "2026-01-19" 6 - category: "infrastructure" 6 + category: "template" 7 7 --- 8 8 9 9 Use this template when your API or third-party service provider is experiencing issues. Based on real-world examples from companies like Vercel, Stripe, and GitHub.
+2 -2
apps/web/src/content/pages/report-template/database-performance-degradation.mdx apps/web/src/content/pages/guides/database-performance-degradation.mdx
··· 1 1 --- 2 2 title: "Database Performance Degradation Template" 3 3 description: "Technical template for communicating database latency and performance issues to your engineering-focused audience." 4 - author: "OpenStatus" 4 + author: "openstatus" 5 5 publishedAt: "2026-01-19" 6 - category: "performance" 6 + category: "template" 7 7 --- 8 8 9 9 Use this template when experiencing database performance issues, elevated latency, or query slowdowns. Ideal for engineering teams and technical stakeholders.
+2 -2
apps/web/src/content/pages/report-template/deployment-rollback.mdx apps/web/src/content/pages/guides/deployment-rollback.mdx
··· 1 1 --- 2 2 title: "Deployment Rollback Template" 3 3 description: "Template for communicating deployment failures and rollback procedures during service disruptions." 4 - author: "OpenStatus" 4 + author: "openstatus" 5 5 publishedAt: "2026-01-19" 6 - category: "deployment" 6 + category: "template" 7 7 --- 8 8 9 9 Use this template when a deployment causes issues and requires rolling back to a previous version. Communicates the issue, mitigation, and resolution clearly.
+2 -2
apps/web/src/content/pages/report-template/feature-degradation.mdx apps/web/src/content/pages/guides/feature-degradation.mdx
··· 1 1 --- 2 2 title: "Feature Degradation Template" 3 3 description: "Template for communicating partial service degradation where some features are impacted but the service remains available." 4 - author: "OpenStatus" 4 + author: "openstatus" 5 5 publishedAt: "2026-01-19" 6 - category: "performance" 6 + category: "template" 7 7 --- 8 8 9 9 Use this template when specific features are degraded or unavailable, but the core service remains operational. Helps users understand what's working and what isn't.
+2 -2
apps/web/src/content/pages/report-template/network-connectivity-issues.mdx apps/web/src/content/pages/guides/network-connectivity-issues.mdx
··· 1 1 --- 2 2 title: "Network Connectivity Issues Template" 3 3 description: "Template for communicating network outages, CDN problems, and regional connectivity issues to affected users." 4 - author: "OpenStatus" 4 + author: "openstatus" 5 5 publishedAt: "2026-01-19" 6 - category: "infrastructure" 6 + category: "template" 7 7 --- 8 8 9 9 Use this template when experiencing network-related issues affecting user connectivity, including CDN problems, regional outages, or ISP issues.
+2 -2
apps/web/src/content/pages/report-template/scheduled-maintenance.mdx apps/web/src/content/pages/guides/scheduled-maintenance.mdx
··· 1 1 --- 2 2 title: "Scheduled Maintenance Template" 3 3 description: "Proactive template for communicating planned maintenance windows, upgrades, and scheduled downtime to users." 4 - author: "OpenStatus" 4 + author: "openstatus" 5 5 publishedAt: "2026-01-19" 6 - category: "maintenance" 6 + category: "template" 7 7 --- 8 8 9 9 Use this template for planned maintenance, infrastructure upgrades, or scheduled downtime. Proactive communication builds trust and reduces support burden.
+2 -2
apps/web/src/content/pages/report-template/security-incident-response.mdx apps/web/src/content/pages/guides/security-incident-response.mdx
··· 1 1 --- 2 2 title: "Security Incident Response Template" 3 3 description: "Formal template for communicating security issues with professional, reassuring language that maintains user trust." 4 - author: "OpenStatus" 4 + author: "openstatus" 5 5 publishedAt: "2026-01-19" 6 - category: "security" 6 + category: "template" 7 7 --- 8 8 9 9 Use this template when investigating or responding to security-related incidents. Maintains professional tone while reassuring users about security measures.
+7 -3
apps/web/src/content/utils.ts
··· 91 91 ); 92 92 } 93 93 94 - export function getReportTemplates(): MDXData[] { 94 + export function getGuides(): MDXData[] { 95 95 return getMDXDataFromDir( 96 - path.join(process.cwd(), "src", "content", "pages", "report-template"), 97 - "/report-template", 96 + path.join(process.cwd(), "src", "content", "pages", "guides"), 97 + "/guides", 98 98 ); 99 99 } 100 100 ··· 158 158 "unrelated", 159 159 "compare", 160 160 "tools", 161 + "guides", 161 162 "all", 162 163 ] as const; 163 164 ··· 177 178 return getComparePages(); 178 179 case "tools": 179 180 return getToolsPages(); 181 + case "guides": 182 + return getGuides(); 180 183 case "all": 181 184 return [ 182 185 ...getBlogPosts(), ··· 185 188 ...getUnrelatedPages(), 186 189 ...getComparePages(), 187 190 ...getToolsPages(), 191 + ...getGuides(), 188 192 ]; 189 193 default: 190 194 throw new Error(`Unknown page type: ${type}`);
+2 -2
apps/web/src/data/content.ts
··· 31 31 href: "https://docs.openstatus.dev", 32 32 }, 33 33 { 34 - label: "Report Templates", 35 - href: "/report-template", 34 + label: "Guides", 35 + href: "/guides", 36 36 }, 37 37 { 38 38 label: "External Status",