Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ description: "Git worktrees have been around since 2015. For most of that time,
author: avi
date: 2026-01-27
category: Engineering
relatedSlugs:
- terminal-daemon-deep-dive
- how-to-get-hit
---

Git worktrees have been around since 2015. For most of that time, they were a curiosity — something kernel developers used, something that showed up in obscure Stack Overflow answers, something most developers had never heard of.
Expand Down
5 changes: 4 additions & 1 deletion apps/marketing/content/blog/how-to-get-hit.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
---
title: How to get hit (probably in the face but anywhere else works too)
description: I ran out of technical content to write for the blog so here is something I am interested in instead.
description: I ran out of technical content to write for the blog so here is something I am interested in instead.
author: Kiet Ho
date: 2026-01-28
category: Company
relatedSlugs:
- terminal-daemon-deep-dive
- git-worktrees-history-deep-dive
---

I like fighting in most forms. I like to watch fights, train fighting, compete, and watch breakdowns. Here's something I learned in training and sparring that I feel is applicable to my own life, perhaps in startups and competition.
Expand Down
3 changes: 3 additions & 0 deletions apps/marketing/content/blog/terminal-daemon-deep-dive.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ description: "How we built a process-isolated terminal host that survives app re
author: Avi Peltz
date: 2026-01-26
category: Engineering
relatedSlugs:
- git-worktrees-history-deep-dive
- how-to-get-hit
---

One of the slickest features of Superset is how our terminal survives app restarts. This is a deep dive on how we built it. Huge credits to [Andreas Asprou](https://x.com/andyasprou) who spearheaded this entire effort.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ import { ArrowLeft } from "lucide-react";
import Link from "next/link";
import type { ReactNode } from "react";
import { AuthorAvatar } from "@/app/blog/components/AuthorAvatar";
import { BlogCard } from "@/app/blog/components/BlogCard";
import { GridCross } from "@/app/blog/components/GridCross";
import { type BlogPost, formatBlogDate, type TocItem } from "@/lib/blog-utils";

interface BlogPostLayoutProps {
post: BlogPost;
toc: TocItem[];
relatedPosts: BlogPost[];
children: ReactNode;
}

export function BlogPostLayout({ post, children }: BlogPostLayoutProps) {
export function BlogPostLayout({
post,
relatedPosts,
children,
}: BlogPostLayoutProps) {
const formattedDate = formatBlogDate(post.date);

return (
Expand Down Expand Up @@ -88,6 +94,22 @@ export function BlogPostLayout({ post, children }: BlogPostLayoutProps) {
<div className="prose max-w-none">{children}</div>
</div>

{/* Related Posts */}
{relatedPosts.length > 0 && (
<section className="relative border-t border-border">
<div className="max-w-3xl mx-auto px-6 py-12">
<h2 className="text-xl font-medium text-foreground mb-6">
Related Posts
</h2>
<div className="grid gap-4 grid-cols-[repeat(auto-fit,minmax(200px,1fr))]">
{relatedPosts.map((relatedPost) => (
<BlogCard key={relatedPost.slug} post={relatedPost} />
))}
</div>
</div>
</section>
)}

{/* Footer */}
<footer className="relative border-t border-border">
<div className="max-w-3xl mx-auto px-6 relative">
Expand Down
13 changes: 11 additions & 2 deletions apps/marketing/src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { MDXRemote } from "next-mdx-remote/rsc";
import { ArticleJsonLd } from "@/components/JsonLd";
import { extractToc, getAllSlugs, getBlogPost } from "@/lib/blog";
import {
extractToc,
getAllSlugs,
getBlogPost,
getRelatedPosts,
} from "@/lib/blog";
import { mdxComponents } from "../components/mdx-components";
import { BlogPostLayout } from "./components/BlogPostLayout";

Expand All @@ -20,6 +25,10 @@ export default async function BlogPostPage({ params }: PageProps) {
}

const toc = extractToc(post.content);
const relatedPosts = getRelatedPosts({
slug,
relatedSlugs: post.relatedSlugs,
});

const url = `${COMPANY.MARKETING_URL}/blog/${slug}`;

Expand All @@ -33,7 +42,7 @@ export default async function BlogPostPage({ params }: PageProps) {
url={url}
image={post.image}
/>
<BlogPostLayout post={post} toc={toc}>
<BlogPostLayout post={post} toc={toc} relatedPosts={relatedPosts}>
<MDXRemote source={post.content} components={mdxComponents} />
</BlogPostLayout>
</main>
Expand Down
8 changes: 4 additions & 4 deletions apps/marketing/src/app/blog/components/BlogCard/BlogCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export function BlogCard({ post }: BlogCardProps) {
const formattedDate = formatBlogDate(post.date);

return (
<Link href={post.url} className="block group">
<article className="border border-border bg-background p-6 transition-all hover:bg-muted/50 hover:border-foreground/20">
<Link href={post.url} className="block group h-full">
<article className="flex flex-col h-full border border-border bg-background p-6 transition-all hover:bg-muted/50 hover:border-foreground/20">
<div className="flex items-center gap-3 mb-3">
<span className="text-xs font-mono text-muted-foreground uppercase tracking-wider">
{post.category}
Expand All @@ -25,11 +25,11 @@ export function BlogCard({ post }: BlogCardProps) {
{post.title}
</h2>
{post.description && (
<p className="text-muted-foreground text-sm leading-relaxed mb-4">
<p className="text-muted-foreground text-sm leading-relaxed mb-4 line-clamp-3">
{post.description}
</p>
)}
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 mt-auto">
<AuthorAvatar
name={post.author}
title="Cofounder, Superset"
Expand Down
1 change: 1 addition & 0 deletions apps/marketing/src/lib/blog-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface BlogPost {
date: string;
category: BlogCategory;
image?: string;
relatedSlugs?: string[];
content: string;
}

Expand Down
21 changes: 21 additions & 0 deletions apps/marketing/src/lib/blog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ function parseFrontmatter(filePath: string): BlogPost | null {
date: dateValue,
category: data.category ?? "News",
image: data.image,
relatedSlugs: data.relatedSlugs,
content,
};
} catch {
Expand Down Expand Up @@ -84,6 +85,26 @@ export function getAllSlugs(): string[] {
.map((f) => f.replace(".mdx", ""));
}

const MAX_RELATED_POSTS = 3;

export function getRelatedPosts({
slug,
relatedSlugs,
}: {
slug: string;
relatedSlugs?: string[];
}): BlogPost[] {
if (relatedSlugs && relatedSlugs.length > 0) {
return relatedSlugs
.map((s) => getBlogPost(s))
.filter((post): post is BlogPost => post !== undefined);
}

return getBlogPosts()
.filter((post) => post.slug !== slug)
.slice(0, MAX_RELATED_POSTS);
}

export function extractToc(content: string): TocItem[] {
const headingRegex = /^(#{2,3})\s+(.+)$/gm;
const toc: TocItem[] = [];
Expand Down
Loading