development
tsx 141 lines 4.55 KB
Raw
1 import type { Metadata } from "next";
2 import { allPosts } from "contentlayer/generated";
3 import Link from "next/link";
4 import { StructuredData } from "@/components/structured-data";
5 import siteMetadata from "@/content/siteMetadata";
6 import { absoluteUrl, genPageMetadata } from "../seo";
7
8 export const metadata: Metadata = {
9 ...genPageMetadata({
10 title: "Blog",
11 description:
12 "Product updates, deployment guides, and infrastructure notes from the smbCloud team.",
13 path: "/posts",
14 keywords: [
15 "smbCloud blog",
16 "deployment guides",
17 "Node.js deployment guide",
18 "Rails deployment guide",
19 "Vapor deployment guide",
20 ],
21 }),
22 };
23
24 const blogStructuredData = {
25 "@context": "https://schema.org",
26 "@graph": [
27 {
28 "@type": "CollectionPage",
29 "@id": absoluteUrl("/posts#webpage"),
30 url: absoluteUrl("/posts"),
31 name: "smbCloud Blog",
32 description:
33 "Product updates, deployment guides, and infrastructure notes from the smbCloud team.",
34 isPartOf: {
35 "@id": absoluteUrl("/#website"),
36 },
37 about: {
38 "@id": absoluteUrl("/#organization"),
39 },
40 },
41 {
42 "@type": "BreadcrumbList",
43 itemListElement: [
44 {
45 "@type": "ListItem",
46 position: 1,
47 name: "Home",
48 item: absoluteUrl("/"),
49 },
50 {
51 "@type": "ListItem",
52 position: 2,
53 name: "Blog",
54 item: absoluteUrl("/posts"),
55 },
56 ],
57 },
58 {
59 "@type": "Blog",
60 name: "smbCloud Blog",
61 url: absoluteUrl("/posts"),
62 description:
63 "Product updates, deployment guides, and infrastructure notes from the smbCloud team.",
64 publisher: {
65 "@id": absoluteUrl("/#organization"),
66 },
67 inLanguage: siteMetadata.language,
68 },
69 ],
70 };
71
72 export default function PostsPage() {
73 let publishedPosts = allPosts.filter((post) => post.published);
74 publishedPosts = publishedPosts.sort(
75 (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
76 );
77
78 return (
79 <div className="mx-auto max-w-3xl px-6 py-16 lg:px-8">
80 <StructuredData data={blogStructuredData} />
81
82 <div className="mb-14">
83 <p className="mb-3 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
84 Blog
85 </p>
86 <h1 className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
87 smbCloud Blog
88 </h1>
89 <p className="mt-3 text-slate-500 dark:text-slate-400">
90 Product updates, deployment guides, and infrastructure notes from the
91 smbCloud team.
92 </p>
93 </div>
94
95 {publishedPosts.length === 0 ? (
96 <p className="text-sm text-slate-400 dark:text-slate-600">
97 No posts published yet. Check back soon.
98 </p>
99 ) : (
100 <div className="space-y-0">
101 {publishedPosts.map((post) => (
102 <article
103 key={post._id}
104 className="group border-b border-slate-200 dark:border-white/[0.06] py-8 first:pt-0 last:border-0"
105 >
106 <Link href={post.slug} className="block">
107 <div className="flex items-start justify-between gap-6">
108 <div className="flex-1 min-w-0">
109 {post.date && (
110 <time
111 dateTime={post.date}
112 className="mb-2 block text-xs text-slate-400 dark:text-slate-600 tabular-nums"
113 >
114 {new Date(post.date).toLocaleDateString("en-US", {
115 year: "numeric",
116 month: "long",
117 day: "numeric",
118 })}
119 </time>
120 )}
121 <h2 className="font-semibold text-slate-900 dark:text-white transition-colors group-hover:text-indigo-600 dark:group-hover:text-indigo-400">
122 {post.title}
123 </h2>
124 {post.description && (
125 <p className="mt-1.5 text-sm leading-relaxed text-slate-500 dark:text-slate-400 line-clamp-2">
126 {post.description}
127 </p>
128 )}
129 </div>
130 <span className="mt-1 shrink-0 text-slate-300 dark:text-slate-700 transition-colors group-hover:text-indigo-500 dark:group-hover:text-indigo-500">
131 →
132 </span>
133 </div>
134 </Link>
135 </article>
136 ))}
137 </div>
138 )}
139 </div>
140 );
141 }