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
11 changes: 7 additions & 4 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ try {
const fm = content.match(/^---\n([\s\S]*?)\n---/);
if (!fm) continue;
const dateMatch = fm[1].match(/^date:\s*["']?(\d{4}-\d{2}-\d{2})["']?\s*$/m);
const updatedMatch = fm[1].match(/^updated:\s*["']?(\d{4}-\d{2}-\d{2})["']?\s*$/m);
const draftMatch = fm[1].match(/^draft:\s*(true|false)\s*$/m);
if (draftMatch && draftMatch[1] === 'true') continue;
if (dateMatch) {
const stamp = updatedMatch?.[1] ?? dateMatch?.[1];
if (stamp) {
const slug = file.replace(/\.(mdx?|md)$/, '');
postLastmod.set(`/blog/${slug}`, new Date(dateMatch[1]));
postLastmod.set(`/blog/${slug}`, new Date(stamp));
}
} catch (err) {
console.warn(
Expand All @@ -43,7 +45,7 @@ try {
}

/** @type {string[]} */
const SKIP_PATTERNS = ['/write'];
const SKIP_PATTERNS = ['/write', '/search'];

/**
* @param {string} path
Expand All @@ -54,6 +56,7 @@ function priorityFor(path) {
if (path === '/blog' || path === '/topics') return 0.9;
if (path === '/playground' || path === '/community' || path === '/contribute') return 0.8;
if (path.startsWith('/blog/')) return 0.7;
if (path.startsWith('/topics/') || path.startsWith('/tags/')) return 0.6;
if (path.startsWith('/authors/')) return 0.6;
return 0.5;
}
Expand All @@ -70,7 +73,7 @@ function changefreqFor(path) {

export default defineConfig({
site: 'https://mlsystems.dev',
trailingSlash: 'ignore',
trailingSlash: 'never',
markdown: {
shikiConfig: { theme: 'github-dark' },
},
Expand Down
5 changes: 3 additions & 2 deletions src/components/ArticleActions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useState } from 'react';
import { SITE } from '@/lib/site';

export default function ArticleActions({
title,
Expand Down Expand Up @@ -77,8 +78,8 @@ export default function ArticleActions({
</button>
</div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--ink-3)' }}>
Cite as: {author.split(' ').reverse().join(', ')}. &quot;{title.split(':')[0]}.&quot;
mlsystems.dev, {date}.
Cite as: {author.split(' ').reverse().join(', ')}. &quot;{title.split(':')[0]}.&quot;{' '}
{SITE.domain}, {date}.
</div>
</div>
);
Expand Down
13 changes: 5 additions & 8 deletions src/components/Comments.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
'use client';

import { useEffect, useRef } from 'react';
import { SITE } from '@/lib/site';

// To enable comments:
// 1. Make the repo public + enable GitHub Discussions.
// 2. Install the giscus app: https://github.com/apps/giscus
// 3. Visit https://giscus.app, fill in MLSysDev/mlsystems.dev + the "Comments"
// 3. Visit https://giscus.app, fill in the repo + the "Comments"
// category, and paste the resulting IDs into the two PUBLIC_* env vars.
// If either ID is missing, this component renders a graceful "not configured"
// note instead of breaking the page.
const REPO = 'MLSysDev/mlsystems.dev';
const REPO = SITE.repo;
const REPO_ID = import.meta.env.PUBLIC_GISCUS_REPO_ID as string | undefined;
const CATEGORY = 'Comments';
const CATEGORY_ID = import.meta.env.PUBLIC_GISCUS_CATEGORY_ID as string | undefined;
Expand Down Expand Up @@ -73,12 +74,8 @@ export default function Comments() {
Giscus
</a>
. Once configured, replies live in{' '}
<a
href="https://github.com/MLSysDev/mlsystems.dev/discussions"
target="_blank"
rel="noreferrer"
>
MLSysDev/mlsystems.dev
<a href={`${SITE.repoUrl}/discussions`} target="_blank" rel="noreferrer">
{SITE.repo}
</a>{' '}
discussions.
</p>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const socialIcons = [
<ul>
<li><a href="/blog">All articles</a></li>
<li><a href="/topics">Topics</a></li>
<li><a href="/blog">Most read</a></li>
<li><a href="/tags">Tags</a></li>
<li><a href="/authors">Authors</a></li>
</ul>
</div>
Expand All @@ -105,7 +105,7 @@ const socialIcons = [
<li><a href="/contribute">Editorial policy</a></li>
<li>
<a
href="https://github.com/MLSysDev/mlsystems.dev/blob/main/CODE_OF_CONDUCT.md"
href={`${SITE.repoUrl}/blob/main/CODE_OF_CONDUCT.md`}
target="_blank"
rel="noreferrer"
>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Nav.astro
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ const isActive = (path: string) => currentPath === path || currentPath.startsWit
const current = document.documentElement.getAttribute('data-theme') || 'light';
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
const themeColor = document.querySelector('meta[name="theme-color"]');
if (themeColor) themeColor.setAttribute('content', next === 'dark' ? '#0e0d0b' : '#f5f1e8');
try {
const raw = localStorage.getItem('mls.prefs');
const prefs = raw ? JSON.parse(raw) : {};
Expand Down
8 changes: 6 additions & 2 deletions src/content/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineCollection, reference, z } from 'astro:content';
import { glob } from 'astro/loaders';
import { TOPIC_IDS } from '@/lib/data';

const authors = defineCollection({
loader: glob({ pattern: '**/*.json', base: './src/content/authors' }),
Expand Down Expand Up @@ -27,9 +28,12 @@ const posts = defineCollection({
authors: z.array(reference('authors')).min(1),
date: z.coerce.date(),
readMin: z.number().int().positive(),
topic: z.string(),
topicId: z.string(),
// Display name is derived from topicId via topicName(); kept optional for
// backward compat with existing frontmatter but no longer rendered.
topic: z.string().optional(),
topicId: z.enum(TOPIC_IDS),
tags: z.array(z.string()).optional(),
updated: z.coerce.date().optional(),
cover: z.union([image(), z.string().url()]).optional(),
featured: z.boolean().optional().default(false),
draft: z.boolean().optional().default(false),
Expand Down
38 changes: 29 additions & 9 deletions src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Base layout for every page — handles fonts, SEO, OG tags, JSON-LD, theme attrs.
// Pages set their own title/description via props; everything else is sensible defaults.

import { APPEARANCE, SITE } from '@/lib/site';
import { APPEARANCE, SITE, SOCIALS } from '@/lib/site';
import Nav from '@/components/Nav.astro';
import Footer from '@/components/Footer.astro';
import AnalyticsConsent from '@/components/AnalyticsConsent.tsx';
Expand All @@ -20,10 +20,18 @@ interface Props {
ogType?: 'website' | 'article';
/** ISO 8601 date — used for article OG metadata */
publishedTime?: string;
/** ISO 8601 date — article last-modified time */
modifiedTime?: string;
/** Article section (topic) — OG article metadata */
section?: string;
/** Article tags — emitted as article:tag OG metadata */
tags?: string[];
/** Alt text for the OG image — defaults to the page title */
imageAlt?: string;
/** Set true to keep the page out of search engine indexes */
noindex?: boolean;
/** JSON-LD structured data — pass an object that gets stringified */
jsonLd?: Record<string, unknown>;
/** JSON-LD structured data — one object or an array of objects, stringified as-is */
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
}

const {
Expand All @@ -33,6 +41,10 @@ const {
image = '/og-default.png',
ogType = 'website',
publishedTime,
modifiedTime,
section,
tags,
imageAlt,
noindex = false,
jsonLd,
} = Astro.props;
Expand All @@ -50,7 +62,7 @@ const siteJsonLd = [
url: SITE.url,
logo: new URL('/favicon-96x96.png', SITE.url).toString(),
description: SITE.description,
sameAs: [SITE.github, 'https://x.com/MLSystemsDev'],
sameAs: [SOCIALS.github, SOCIALS.twitter, SOCIALS.linkedin].filter(Boolean),
},
{
'@context': 'https://schema.org',
Expand Down Expand Up @@ -101,6 +113,11 @@ const siteJsonLd = [
if (prefs && validAccents.indexOf(prefs.accent) !== -1) {
root.setAttribute('data-accent', prefs.accent);
}
const theme = root.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
const meta = document.createElement('meta');
meta.setAttribute('name', 'theme-color');
meta.setAttribute('content', theme === 'dark' ? '#0e0d0b' : '#f5f1e8');
document.head.appendChild(meta);
} catch {}
})();
</script>
Expand All @@ -117,9 +134,7 @@ const siteJsonLd = [
<!-- Robots -->
{noindex && <meta name="robots" content="noindex, nofollow" />}

<!-- Theme color (matches paper bg) -->
<meta name="theme-color" content="#f5f1e8" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#0e0d0b" media="(prefers-color-scheme: dark)" />
<!-- Theme color is set from the resolved data-theme in the pre-paint script above. -->

<!-- Open Graph -->
<meta property="og:type" content={ogType} />
Expand All @@ -129,15 +144,20 @@ const siteJsonLd = [
<meta property="og:image" content={imageUrl} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content={imageAlt ?? fullTitle} />
<meta property="og:site_name" content={SITE.name} />
<meta property="og:locale" content="en_US" />
{publishedTime && <meta property="article:published_time" content={publishedTime} />}
{modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
{section && <meta property="article:section" content={section} />}
{tags?.map((tag) => <meta property="article:tag" content={tag} />)}

<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={fullTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={imageUrl} />
<meta name="twitter:site" content={SITE.twitter} />
<meta name="twitter:creator" content={SITE.twitter} />

<!-- Fonts — preloaded non-blocking. Stylesheet swaps in after first paint. -->
Expand Down Expand Up @@ -210,9 +230,9 @@ const siteJsonLd = [

<body>
<a href="#main" class="skip-link" data-pagefind-ignore>Skip to content</a>
<div data-pagefind-ignore>
<header data-pagefind-ignore>
<Nav currentPath={currentPath} />
</div>
</header>
<main id="main">
<slot />
</main>
Expand Down
19 changes: 19 additions & 0 deletions src/lib/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ export const TOPICS: Topic[] = [
},
];

// Canonical topic IDs — the single source of truth for the taxonomy.
// Used to validate post frontmatter (see src/content/config.ts).
export const TOPIC_IDS = TOPICS.map((t) => t.id) as [string, ...string[]];

const TOPIC_BY_ID = new Map(TOPICS.map((t) => [t.id, t]));

/** Canonical display name for a topic ID; falls back to the ID if unknown. */
export function topicName(id: string): string {
return TOPIC_BY_ID.get(id)?.name ?? id;
}

/** URL-safe slug for a free-text tag (e.g. "KV-cache" → "kv-cache"). */
export function tagSlug(tag: string): string {
return tag
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}

export function countPostsByTopic<T extends { data: { topicId: string } }>(
posts: T[],
): Record<string, number> {
Expand Down
34 changes: 23 additions & 11 deletions src/lib/site.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
// Site-wide constants. Edit before launch.

const GITHUB_ORG = 'MLSysDev';
const GITHUB_REPO = `${GITHUB_ORG}/mlsystems.dev`;
const X_HANDLE = 'MLSystemsDev';

// Social links. Set to null to render as a disabled "coming soon" icon.
export const SOCIALS: Record<'discord' | 'github' | 'twitter' | 'linkedin', string | null> = {
discord: 'https://discord.gg/pxEvXN28tc',
github: `https://github.com/${GITHUB_ORG}`,
twitter: `https://x.com/${X_HANDLE}`,
linkedin: 'https://www.linkedin.com/company/mlsystems-dev',
};

export const SITE = {
name: 'ML Systems',
domain: 'mlsystems.dev',
url: 'https://mlsystems.dev',
description:
'Open community writing about machine learning systemsinference engines, training infrastructure, GPU memory, embeddings, and deployments. Articles, primers, and case studies from anyone learning or building in the space.',
'Deep dives on machine learning systems: inference, training infrastructure, GPU memory, quantization, and LLM architecture — articles, primers, and case studies.',
tagline: 'Machine learning, from kernels to clusters.',
author: 'The ML Systems Community',
twitter: '@MLSystemsDev',
github: 'https://github.com/MLSysDev',
twitter: `@${X_HANDLE}`,
// GitHub identity — single source of truth for the org/repo and derived URLs.
github: SOCIALS.github as string,
org: GITHUB_ORG,
repo: GITHUB_REPO,
repoUrl: `https://github.com/${GITHUB_REPO}`,
issuesUrl: `https://github.com/${GITHUB_REPO}/issues`,
pullsUrl: `https://github.com/${GITHUB_REPO}/pulls`,
discussionsUrl: `https://github.com/${GITHUB_REPO}/discussions`,
orgDiscussionsUrl: `https://github.com/orgs/${GITHUB_ORG}/discussions`,
pitchEmail: 'admin@mlsystems.dev',
// Used in nav, footer, etc.
startYear: 2026,
Expand All @@ -22,11 +42,3 @@ export const APPEARANCE = {
background: 'plain',
density: 'comfortable',
} as const;

// Social links. Set to null to render as a disabled "coming soon" icon.
export const SOCIALS: Record<'discord' | 'github' | 'twitter' | 'linkedin', string | null> = {
discord: 'https://discord.gg/pxEvXN28tc',
github: 'https://github.com/MLSysDev',
twitter: 'https://x.com/MLSystemsDev',
linkedin: 'https://www.linkedin.com/company/mlsystems-dev',
};
8 changes: 6 additions & 2 deletions src/pages/about.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
import { SITE } from '@/lib/site';

const GITHUB_URL = 'https://github.com/HumbleBee14';
const ADMIN_EMAIL = 'admin@mlsystems.dev';
const ADMIN_EMAIL = SITE.pitchEmail;

const links = [
{
Expand All @@ -21,7 +21,11 @@ const links = [
];
---

<BaseLayout title="About" description={`The story behind ${SITE.name}.`} image="/og/page/about.png">
<BaseLayout
title="About"
description="Why ML Systems exists — an open, community-written archive of articles, primers, and tools on machine learning systems, from inference and training to LLM architecture."
image="/og/page/about.png"
>
<div class="container blog-shell">
<div class="about-head">
<div class="eyebrow page-eyebrow">About</div>
Expand Down
Loading
Loading