Code — how this page is built
Under the hood
The actual source of the components on this page, read straight from the repo. Copy it, read it, or open it on GitHub.
Article readerview on GitHub ↗
The split-layout reader: text left (Courier field-note, H1, dek, TL;DR box, MDX body, FAQ, prev/next), placeholder images right; stacks to one column under 760px. MDX is compiled server-side with remark-gfm so tables render. Emits Article + FAQPage JSON-LD.
import type { Metadata } from 'next';
import type { ComponentProps } from 'react';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { MDXRemote } from 'next-mdx-remote/rsc';
import remarkGfm from 'remark-gfm';
import { Jsonld } from '@/components/Jsonld';
import { UpdatedStamp } from '@/components/UpdatedStamp';
import { ArticleReaderShell, Fig } from '@/components/articles/figure-sync';
import { PageFrame } from '@/components/shell/PageFrame';
import { getAllArticles, getArticle, type Article } from '@/lib/content';
import type { CodeRef, TaggingData } from '@/lib/lens';
import { normalizeView } from '@/lib/lens';
import { absoluteUrl, articleJsonLd, faqJsonLd, pageMetadata, type JsonLd } from '@/lib/seo';
import styles from './reader.module.css';
const mdxOptions = { mdxOptions: { remarkPlugins: [remarkGfm] } } as const;
/** External links open in a new tab; internal ones don't. */
function MdxLink({ href = '', ...props }: ComponentProps<'a'>) {
const external = /^https?:\/\//.test(href);
return <a href={href} {...(external ? { target: '_blank', rel: 'noreferrer' } : {})} {...props} />;
}
const mdxComponents = { Fig, a: MdxLink };
export function generateStaticParams() {
return getAllArticles().map((a) => ({ slug: a.slug }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const article = getArticle(slug);
if (!article) return {};
return pageMetadata({
title: article.title,
description: article.dek || article.tldr || article.title,
path: `/articles/${article.slug}`,
type: 'article',
publishedTime: article.date,
modifiedTime: article.updated ?? article.date,
// Hero as the link-preview image (og:image / twitter:image) so shared links
// show the article's primary photo instead of the site favicon.
images: article.hero?.startsWith('/') ? [absoluteUrl(article.hero)] : undefined,
});
}
/** Neighbouring articles (newest-first list) for the prev/next pager. */
function neighbours(slug: string) {
const all = getAllArticles();
const idx = all.findIndex((a) => a.slug === slug);
return {
index: idx,
total: all.length,
prev: idx > 0 ? all[idx - 1] : null,
next: idx >= 0 && idx < all.length - 1 ? all[idx + 1] : null,
};
}
function Reader({ article }: { article: Article }) {
const { index, total, prev, next } = neighbours(article.slug);
const fieldNo = String(total - index).padStart(2, '0');
const year = article.date.slice(0, 4);
const left = (
<>
<div className={styles.fieldnote}>
Field note {fieldNo} — {year}
</div>
<h1 className={styles.h1}>{article.title}</h1>
{article.dek ? <div className={styles.dek}>{article.dek}</div> : null}
{article.tldr ? (
<div className={styles.tldr}>
<div className={styles.tldrLabel}>TL;DR</div>
<p className={styles.tldrText}>{article.tldr}</p>
</div>
) : null}
<div className={styles.prose}>
{/* @ts-expect-error Async Server Component */}
<MDXRemote source={article.content} options={mdxOptions} components={mdxComponents} />
{article.faq && article.faq.length > 0 ? (
<>
<h2>FAQ</h2>
{article.faq.map((f, i) => (
<div key={i}>
<h3>{f.question}</h3>
<p>{f.answer}</p>
</div>
))}
</>
) : null}
</div>
<div className={styles.pager}>
{prev ? (
<Link href={`/articles/${prev.slug}`}>← {prev.title}</Link>
) : (
<span className={styles.pagerSpacer}>—</span>
)}
<Link href="/articles">index</Link>
{next ? (
<Link href={`/articles/${next.slug}`}>{next.title} →</Link>
) : (
<span className={styles.pagerSpacer}>—</span>
)}
</div>
<div style={{ marginTop: 18 }} className={styles.fieldnote}>
{article.updated ? <UpdatedStamp date={article.updated} /> : null}
</div>
</>
);
return (
<div className={styles.root}>
<ArticleReaderShell left={left} />
</div>
);
}
export default async function ArticleReaderPage({
params,
searchParams,
}: {
params: Promise<{ slug: string }>;
searchParams: Promise<{ view?: string }>;
}) {
const { slug } = await params;
const { view: rawView } = await searchParams;
const view = normalizeView(rawView);
const article = getArticle(slug);
if (!article) notFound();
const path = `/articles/${article.slug}`;
const jsonLd: JsonLd[] = [
articleJsonLd({
title: article.title,
description: article.dek || article.tldr || article.title,
path,
datePublished: article.date,
dateModified: article.updated,
techArticle: article.techArticle,
}),
];
if (article.faq && article.faq.length > 0) {
jsonLd.push(faqJsonLd(article.faq));
}
const headings: TaggingData['headings'] = [{ level: 1, text: article.title }];
for (const line of article.content.split('\n')) {
if (line.startsWith('## ')) headings.push({ level: 2, text: line.slice(3).trim() });
else if (line.startsWith('### ')) headings.push({ level: 3, text: line.slice(4).trim() });
}
if (article.faq?.length) headings.push({ level: 2, text: 'FAQ' });
const tagging: TaggingData = {
title: article.title,
description: article.dek || article.tldr || article.title,
canonical: absoluteUrl(path),
updated: article.updated ?? article.date,
headings,
jsonLd,
};
const code: CodeRef[] = [
{
title: 'Article reader',
file: 'app/articles/[slug]/page.tsx',
explanation:
'The split-layout reader: text left (Courier field-note, H1, dek, TL;DR box, MDX body, FAQ, prev/next), placeholder images right; stacks to one column under 760px. MDX is compiled server-side with remark-gfm so tables render. Emits Article + FAQPage JSON-LD.',
},
{
title: `${article.slug}.mdx`,
file: `content/articles/${article.slug}.mdx`,
explanation:
'The article itself — frontmatter (title, dek, tldr, dates, tags, faq) plus the MDX body. This file is the whole article; the reader is just its frame.',
},
];
return (
<>
<Jsonld data={jsonLd} />
<PageFrame
view={view}
basePath={path}
content={<Reader article={article} />}
tagging={tagging}
code={code}
/>
</>
);
}
article-tier-list.mdxview on GitHub ↗
The article itself — frontmatter (title, dek, tldr, dates, tags, faq) plus the MDX body. This file is the whole article; the reader is just its frame.
---
title: "It is time for my own article tier list."
slug: article-tier-list
dek: "Or just glaze my own literary genius. I haven’t decided yet."
date: 2026-06-25
updated: 2026-06-25
author: kaspirius
tags: [meta, tier-list, writing]
hero: /articles/article-tier-list/hero.jpg
---
<Fig src="/articles/article-tier-list/hero.jpg" cover alt="A tier list grading the seven articles on how interesting, how well executed, and vibe — with an overall score out of 21" caption="the ratings" />
I post an article a day, everyday, and when I’ve done a week, I’ll make a tier list and discuss things people have mentioned. Or just glaze my own literary genius. I haven’t decided yet.
As you can see on the picture on the right, you can see I have graded them out of 7 in three different factors. How interesting they are, how well executed they were, and a vibe score. Giving a perfect score of getting 21 and a worst score of achieving 3.
I’ll talk about the rubric once here in this first tier list and then never again.
How interesting they are is a funny one. Purely because I wouldn’t have written on the topic if I didn’t find it interesting. But obviously my patience to write ebbs and flows, I am human after all and therefore contain multitudes. And that patience impacts the articles in the sense of how much research am I willing to do, how much thinking am I willing to do and do I have the time for some thing long or something short.
How well executed something is dictates whether I am proud of my work. This does not actually refer to whether others agree with me, or whether I feel like the work is done. There may be followups to certain topics and there may be nothing. Regardless of how well it was executed, there may be followups. This is purely based on if someone asks me to provide an example of what I’ve written, in which order would I provide the articles.
Vibe score. This is mostly just vibes. This is mostly just a combination of did I have fun conversations about the topic after, did I like the pictures I included, are there any funny jokes, as well as the two prior pieces.
## 1. [What AI-generated images actually cost (and what they replace)](/articles/ai-art-what-it-costs)
This was my first one. Felt pretty good about it. I liked the idea of circumventing expectations with the title and content difference. I think that there is worthwhile work to be done in terms of future analysis and seeing where models go from here. It would be interesting perhaps to walk around a town but when there is decision to be made. We need to look at an ai generated photo of a description of the place and follow it blind. Who knows. I think there is something to say around the role of ai art in terms of making story telling EASIER but I dont really want to use it for that. A lot of my articles are taking photos from online without credit, just like our forefathers foretold we should.
## 2. [China vs USA, long term vs short term, governments & economies](/articles/china-vs-usa)
I mean I love the concept of debating china vs the US. And I think that there is a lot of cool stuff coming out of China. But it’s still quite unknown really, to me atleast. I haven’t been there myself for example. But this did feel like a topic that was quickly becoming more than I meant to write about on here. And I also proceeded to mention a bunch of side topics (the topic of chinese EV’s) and random tangents (indigenous rights in Canada). I think there is a good bones here, but obviously once we start talking about things like the Uyghurs, not doing the homework and putting proper effort in reduces the overall value of the writing. But going back to my point on patience, writing an article a day, I cannot give it the work it deserves.
## 3. [The failure of the airport layover](/articles/airport-layover)
Holy fuck did I enjoy writing this one. I mean what the hell is going on with my vocab here. I wrote the word fuck here 40 times. Compared to the average 2-3 to display emphasis. It was also one of the writings I did that invoked literal feelings in me as I wrote (disgust and anger in this case). I still do believe in a waterpark layover, but I have taken it to the next level. You know how sometimes a plane will drop you off and you gotta take some stairs and then a bus?
A waterslide. Directly into the waterpark.
## 4. [Why prior generations looked older while young](/articles/retrospective-aging)
I do not have a fucking clue why I wrote this. Not a singular fucking clue actually. I mean its an interesting topic but its also a bit of a dead horse. Like I get it, lead gasoline, people smoked, old people are old and smelly. We get it.
I think I wanted to talk about Brandy. And I do see a lot of that intergenerational similarities in dogs and humans. But this is now something out of scope for me, something already researched by others, and really lacking in meaningful output (Okay so if I prove generations look the same, then what?)
## 5. [Remote Learning, AI in Education, and the adverse incentives of change](/articles/ai-in-education)
As someone who works in AI and with AI frequently, I wanted to tackle this. I mean I find it hilarious looking at stories of AI psychosis (not funny haha but funny weird). But I do genuinely fear for the generation being raised with a tool like this. I mean I guess older generations probably feared what would happen if people had google in their pockets but here we are. I just do think it represents a turning point and it will be very interesting to look back on in 5-10 years when I think about the way I raise my kids.
## 6. [The Blueberry Pastry tier list](/articles/blueberry-pastry-tier-list)
Banger. Knocked it out of the park on this one. Mostly because I annoyed my father, gave my mother a nice nostalgic reminder of the past, and annoyed a couple friends in the process. Man I did not expect this to be so controversial. People were quicker to accept a waterpark at an airport than a muffin being better than a pie (although the final consensus post debate does state I was correct. A goated muffin is superior to a goated pie.
but as my good friend Aprameya said. Aite, im done with this and don’t want to pick this up later.
## 7. [Why you were obsessed with Quick Sand & Piranhas as a child](/articles/quicksand-piranhas)
There was a lot of childlike wonder doing this one. Starting with the topic itself, it’s fun to think back on the misconceptions you had a kid. I used to be scared running in a field because what if suddenly quicksand. Like fucking hell, quicksand? In denmark? Localised entirely within my kitchen? No you can’t see it.
But it actually got more interesting as I looked into it. I had a bit more time for research that day than some of the other articles and it paid off. I mean fuck me, learning about the dreadful pennies, the transition that gave us the daily mail and how fucking Roosevelt can be revered for his work on the Great Depression, world war 2, and the fucking perpetuation of piranhas in the modern mythos. Goddamn.
## Final ratings
As you can see by my ratings our overall winner is the Blueberries. Followed by a tied second and third between the quicksand and AI generated images.
I mean the blueberries got it all. Jokes. Some funny even. It got discussion. It got anecdotes and nostalgia. It has pictures of yummy food and it was a joy to write. Whats not to love?
The quicksand and AI photo one both had some analysis. Some deep diving and thoughts. Some genuine rewarding curiosity behind them. And I think thats why they did so well.
msg me on instagram if you got your own thoughts. I’ll add a lil editors note if anyone has something important to say. If you read them all, I appreciate that. If you didn’t but read this, just read the top three. Bless up.