Skip to content

Commit 81232d5

Browse files
authored
Introducing the Vortex Community Blog (#14)
Each post is pretty basic markdown file, rendered using gray-matter. Posts can be hidden, and can have multiple authors. I've tried to keep the design pretty consistent with the existing website, I think it looks ok but its basically dark-mode only. Also removed bun support to have an obvious package manager for the project, mostly so vercel can pick it up. Signed-off-by: Adam Gutglick <adam@spiraldb.com>
1 parent 379553d commit 81232d5

File tree

12 files changed

+1179
-861
lines changed

12 files changed

+1179
-861
lines changed

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,14 @@ pnpm install
2323
pnpm dev
2424
```
2525

26-
or
27-
28-
```bash
29-
bun install
30-
bun dev
31-
```
32-
3326
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
3427

3528
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
3629

3730
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
3831

32+
**Note:** The deployment is managed by `pnpm`, so any dependency change should be managed by it and reflected in `pnpm-lock.yaml`.
33+
3934
## Learn More
4035

4136
To learn more about Next.js, take a look at the following resources:

bun.lock

Lines changed: 0 additions & 846 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
},
1111
"dependencies": {
1212
"@vercel/analytics": "^1.5.0",
13+
"gray-matter": "^4.0.3",
1314
"next": "15.2.4",
1415
"next-plausible": "^3.12.4",
1516
"ogl": "^1.0.11",
1617
"prettier": "^3.5.3",
1718
"react": "^19.0.0",
1819
"react-dom": "^19.0.0",
20+
"react-markdown": "^10.1.0",
1921
"use-scramble": "^2.2.15"
2022
},
2123
"devDependencies": {

pnpm-lock.yaml

Lines changed: 752 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { getPostBySlug, getAllSlugs } from '@/lib/blog';
2+
import { notFound } from 'next/navigation';
3+
import ReactMarkdown from 'react-markdown';
4+
import Link from 'next/link';
5+
import { Metadata } from 'next';
6+
7+
interface BlogPostPageProps {
8+
params: Promise<{ slug: string }>;
9+
}
10+
11+
export async function generateStaticParams() {
12+
const slugs = getAllSlugs();
13+
return slugs.map((slug) => ({
14+
slug,
15+
}));
16+
}
17+
18+
export async function generateMetadata({ params }: BlogPostPageProps): Promise<Metadata> {
19+
const { slug } = await params;
20+
const post = getPostBySlug(slug);
21+
22+
if (!post) {
23+
return {
24+
title: 'Post Not Found | Vortex Blog',
25+
};
26+
}
27+
28+
return {
29+
title: `${post.title} | Vortex Blog`,
30+
description: post.excerpt || `Read "${post.title}" on the Vortex blog`,
31+
openGraph: {
32+
title: `${post.title} | Vortex Blog`,
33+
description: post.excerpt || `Read "${post.title}" on the Vortex blog`,
34+
siteName: "Vortex",
35+
url: `https://vortex.dev/blog/${slug}`,
36+
type: "article",
37+
locale: "en_US",
38+
publishedTime: post.date,
39+
},
40+
alternates: {
41+
canonical: `https://vortex.dev/blog/${slug}`
42+
}
43+
};
44+
}
45+
46+
export default async function BlogPostPage({ params }: BlogPostPageProps) {
47+
const { slug } = await params;
48+
const post = getPostBySlug(slug);
49+
50+
if (!post) {
51+
notFound();
52+
}
53+
54+
return (
55+
<div className="min-h-screen w-full bg-background text-white">
56+
<div className="max-w-4xl mx-auto px-4 py-16">
57+
{/* Back to blog link */}
58+
<Link
59+
href="/blog"
60+
className="inline-flex items-center gap-2 text-grey font-mono text-sm hover:text-white transition-colors mb-8"
61+
>
62+
← Back to blog
63+
</Link>
64+
65+
{/* Post header */}
66+
<header className="mb-12">
67+
<h1 className="text-3xl md:text-5xl font-funnel font-light text-white mb-6">
68+
{post.title}
69+
</h1>
70+
71+
<div className="flex flex-col md:flex-row md:items-center gap-4 md:gap-8 text-grey font-mono text-sm">
72+
<time dateTime={post.date}>
73+
{new Date(post.date).toLocaleDateString('en-US', {
74+
year: 'numeric',
75+
month: 'long',
76+
day: 'numeric'
77+
})}
78+
</time>
79+
80+
<div className="flex items-center gap-2">
81+
<span>by</span>
82+
<span className="text-white">
83+
{post.authors.join(', ')}
84+
</span>
85+
</div>
86+
</div>
87+
</header>
88+
89+
{/* Post content */}
90+
<article className="dashed-top p-6 md:p-8">
91+
<div className="prose prose-invert prose-lg max-w-none">
92+
<ReactMarkdown
93+
components={{
94+
h1: ({ children }) => (
95+
<h1 className="text-2xl md:text-3xl font-sans font-semibold text-white mt-8 mb-4 first:mt-0">
96+
{children}
97+
</h1>
98+
),
99+
h2: ({ children }) => (
100+
<h2 className="text-xl md:text-2xl font-sans font-semibold text-white mt-8 mb-4">
101+
{children}
102+
</h2>
103+
),
104+
h3: ({ children }) => (
105+
<h3 className="text-lg md:text-xl font-sans font-semibold text-white mt-6 mb-3">
106+
{children}
107+
</h3>
108+
),
109+
p: ({ children }) => (
110+
<p className="text-grey font-sans text-base leading-relaxed mb-4">
111+
{children}
112+
</p>
113+
),
114+
ul: ({ children }) => (
115+
<ul className="list-disc list-inside text-grey font-sans text-base leading-relaxed mb-4 space-y-2">
116+
{children}
117+
</ul>
118+
),
119+
ol: ({ children }) => (
120+
<ol className="list-decimal list-inside text-grey font-sans text-base leading-relaxed mb-4 space-y-2">
121+
{children}
122+
</ol>
123+
),
124+
li: ({ children }) => (
125+
<li className="text-grey">
126+
{children}
127+
</li>
128+
),
129+
blockquote: ({ children }) => (
130+
<blockquote className="border-l-4 border-white pl-4 italic text-grey mb-4">
131+
{children}
132+
</blockquote>
133+
),
134+
code: ({ children, className }) => {
135+
const isInline = !className;
136+
if (isInline) {
137+
return (
138+
<code className="bg-white/10 text-white font-mono text-sm px-1.5 py-0.5 rounded">
139+
{children}
140+
</code>
141+
);
142+
}
143+
return (
144+
<code className="block bg-white/5 text-white font-mono text-sm p-4 rounded overflow-x-auto mb-4">
145+
{children}
146+
</code>
147+
);
148+
},
149+
pre: ({ children }) => (
150+
<pre className="bg-white/5 text-white font-mono text-sm p-4 rounded overflow-x-auto mb-4">
151+
{children}
152+
</pre>
153+
),
154+
a: ({ children, href }) => (
155+
<a
156+
href={href}
157+
className="text-white underline hover:text-grey transition-colors"
158+
target={href?.startsWith('http') ? '_blank' : undefined}
159+
rel={href?.startsWith('http') ? 'noopener noreferrer' : undefined}
160+
>
161+
{children}
162+
</a>
163+
),
164+
strong: ({ children }) => (
165+
<strong className="text-white font-semibold">
166+
{children}
167+
</strong>
168+
),
169+
em: ({ children }) => (
170+
<em className="text-white italic">
171+
{children}
172+
</em>
173+
),
174+
}}
175+
>
176+
{post.content}
177+
</ReactMarkdown>
178+
</div>
179+
</article>
180+
</div>
181+
</div>
182+
);
183+
}

src/app/blog/page.tsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { getAllPosts } from "@/lib/blog";
2+
import { Metadata } from "next";
3+
import Link from "next/link";
4+
5+
export const metadata: Metadata = {
6+
title: "Blog | Vortex",
7+
description:
8+
"Latest updates and insights from the Vortex team about columnar file formats, performance optimization, and cutting-edge research.",
9+
openGraph: {
10+
title: "Blog | Vortex",
11+
description:
12+
"Latest updates and insights from the Vortex team about columnar file formats, performance optimization, and cutting-edge research.",
13+
siteName: "Vortex",
14+
url: "https://vortex.dev/blog",
15+
type: "website",
16+
locale: "en_US"
17+
},
18+
alternates: {
19+
canonical: "https://vortex.dev/blog"
20+
}
21+
};
22+
23+
export default function BlogPage() {
24+
const posts = getAllPosts();
25+
26+
return (
27+
<div className="min-h-screen w-full bg-background text-white">
28+
<div className="max-w-4xl mx-auto px-4 py-16">
29+
<div className="mb-12">
30+
<h1 className="text-4xl md:text-6xl font-funnel font-light text-white mb-4">
31+
Blog
32+
</h1>
33+
<p className="text-grey font-mono text-lg">
34+
Updates and insights from the Vortex team
35+
</p>
36+
</div>
37+
38+
{posts.length === 0 ? (
39+
<div className="dashed-top dashed-bottom p-8">
40+
<p className="text-grey font-mono text-center">
41+
No blog posts yet. Check back soon for updates!
42+
</p>
43+
</div>
44+
) : (
45+
<div className="space-y-6">
46+
{posts.map((post) => (
47+
<Link
48+
key={post.slug}
49+
href={`/blog/${post.slug}`}
50+
className="block group"
51+
>
52+
<article className="dashed-top dashed-bottom p-6 hover:bg-white/5 transition-colors">
53+
<div className="flex flex-col md:flex-row md:items-center md:justify-between mb-3">
54+
<h2 className="text-xl md:text-2xl font-sans font-medium text-white group-hover:text-grey transition-colors">
55+
{post.title}
56+
</h2>
57+
<time
58+
dateTime={post.date}
59+
className="text-grey font-mono text-sm mt-2 md:mt-0"
60+
>
61+
{new Date(post.date).toLocaleDateString("en-US", {
62+
year: "numeric",
63+
month: "long",
64+
day: "numeric"
65+
})}
66+
</time>
67+
</div>
68+
69+
<div className="flex flex-col md:flex-row md:items-center md:justify-between">
70+
{post.excerpt && (
71+
<p className="text-grey font-sans text-base mb-3 md:mb-0 md:mr-6">
72+
{post.excerpt}
73+
</p>
74+
)}
75+
76+
<div className="flex items-center gap-2">
77+
<span className="text-grey font-mono text-sm">by</span>
78+
<span className="text-white font-mono text-sm">
79+
{post.authors.join(", ")}
80+
</span>
81+
</div>
82+
</div>
83+
</article>
84+
</Link>
85+
))}
86+
</div>
87+
)}
88+
</div>
89+
</div>
90+
);
91+
}

src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default async function RootLayout({
2929
return (
3030
<html lang="en">
3131
<body
32-
className={`${geistSans.variable} ${geistMono.variable} ${funnelDisplay.variable} antialiased overflow-hidden`}
32+
className={`${geistSans.variable} ${geistMono.variable} ${funnelDisplay.variable} antialiased`}
3333
>
3434
<PlausibleProvider
3535
domain={process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN ?? ""}

src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const metadata: Metadata = {
2222

2323
export default function Home() {
2424
return (
25-
<div className="h-full w-full relative">
25+
<div className="h-full w-full relative overflow-hidden">
2626
<HeroASCII />
2727
</div>
2828
);

src/app/sitemap.xml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
33
<url>
4-
<loc>https://vortex.dev</loc>
5-
<lastmod>2025-04-07T00:00:00.000Z</lastmod>
6-
<changefreq>none</changefreq>
7-
<priority>1.0</priority>
4+
<loc>https://vortex.dev</loc>
5+
<lastmod>2025-09-08T00:00:00.000Z</lastmod>
6+
<changefreq>none</changefreq>
7+
<priority>1.0</priority>
88
</url>
9+
<url>
10+
<loc>https://vortex.dev/blog</loc>
11+
<lastmod>2025-09-08T00:00:00.000Z</lastmod>
12+
<changefreq>weekly</changefreq>
13+
<priority>1.0</priority>
14+
</url>
915
</urlset>

src/components/layout/header.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ export const Header = () => {
4848
>
4949
Bench
5050
</Link>
51+
<Link
52+
href="/blog"
53+
className="uppercase text-white font-mono text-base md:text-[18px] font-medium"
54+
>
55+
Blog
56+
</Link>
5157
</div>
5258
</div>
5359
);

0 commit comments

Comments
 (0)