Skip to content
Open
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
9 changes: 8 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { defineConfig } from "astro/config";
import prefetch from "@astrojs/prefetch";
import sitemap from "@astrojs/sitemap";
Comment on lines 2 to +3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove deprecated @astrojs/prefetch integration.

As noted in package.json, @astrojs/prefetch is incompatible with Astro 5. Remove the import and use built-in prefetching.

 import { defineConfig } from "astro/config";
-import prefetch from "@astrojs/prefetch";
 import sitemap from "@astrojs/sitemap";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import prefetch from "@astrojs/prefetch";
import sitemap from "@astrojs/sitemap";
import { defineConfig } from "astro/config";
import sitemap from "@astrojs/sitemap";
🤖 Prompt for AI Agents
In `@astro.config.mjs` around lines 2 - 3, Remove the deprecated integration
import and any references to it: delete the line importing prefetch ("import
prefetch from \"@astrojs/prefetch\";") and remove "prefetch" from the Astro
integrations list or any place it's used in astro.config.mjs; rely on Astro 5's
built-in prefetching and remove any configuration or code that expects the
`@astrojs/prefetch` integration (search for the symbol "prefetch" to find usages).

import tailwindcss from "@tailwindcss/vite";

// https://astro.build/config
export default defineConfig({
site: "https://t3.gg",
image: {
// Apply responsive image defaults to Markdown content.
layout: "constrained",
domains: ["t3.gg", "img.youtube.com"],
},
vite: {
plugins: [tailwindcss()],
},
plugins: [],
integrations: [prefetch()],
integrations: [prefetch(), sitemap()],
Comment on lines 7 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Site and image configuration look good; update integrations.

The site URL is required for sitemap/RSS generation, and the image configuration properly enables responsive images with appropriate domains.

Update the integrations array to remove prefetch and optionally enable built-in prefetching:

 export default defineConfig({
   site: "https://t3.gg",
+  prefetch: true,
   image: {
     layout: "constrained",
     domains: ["t3.gg", "img.youtube.com"],
   },
   // ...
-  integrations: [prefetch(), sitemap()],
+  integrations: [sitemap()],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default defineConfig({
site: "https://t3.gg",
image: {
// Apply responsive image defaults to Markdown content.
layout: "constrained",
domains: ["t3.gg", "img.youtube.com"],
},
vite: {
plugins: [tailwindcss()],
},
plugins: [],
integrations: [prefetch()],
integrations: [prefetch(), sitemap()],
export default defineConfig({
site: "https://t3.gg",
prefetch: true,
image: {
// Apply responsive image defaults to Markdown content.
layout: "constrained",
domains: ["t3.gg", "img.youtube.com"],
},
vite: {
plugins: [tailwindcss()],
},
plugins: [],
integrations: [sitemap()],
});
🤖 Prompt for AI Agents
In `@astro.config.mjs` around lines 7 - 18, Remove the prefetch() integration from
the integrations array and instead enable Astro's built-in prefetching;
specifically, edit the defineConfig block to drop prefetch() from integrations
(leave integrations: [sitemap()]) and optionally add an experimental prefetch
flag inside the config (e.g., add an experimental: { prefetch: true } property)
so built-in prefetching is used rather than the external prefetch() integration.

redirects: {
"/links": "/",
"/faq": "/",
Expand Down
42 changes: 31 additions & 11 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
},
"dependencies": {
"@astrojs/prefetch": "^0.4.1",
"@astrojs/rss": "^4.0.15",
"@astrojs/sitemap": "^3.7.0",
"date-fns": "^4.1.0"
Comment on lines 24 to 27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat package.json | head -50

Repository: T3-Content/t3.gg

Length of output: 833


🏁 Script executed:

test -f astro.config.mjs && cat astro.config.mjs || echo "astro.config.mjs not found"

Repository: T3-Content/t3.gg

Length of output: 624


🌐 Web query:

Astro 5 built-in prefetch configuration

💡 Result:

Astro 5 has built-in link prefetching (the old @astrojs/prefetch integration is deprecated/replaced). [5]

Enable it (project-wide)

In astro.config.mjs:

import { defineConfig } from 'astro/config';

export default defineConfig({
  prefetch: true,
});

This injects the prefetch runtime so you can opt-in per link with data-astro-prefetch. [1][2]

Prefetch configuration options

You can also pass an object:

export default defineConfig({
  prefetch: {
    defaultStrategy: 'viewport', // 'hover' | 'tap' | 'viewport' | 'load'
    prefetchAll: false,          // true = prefetch all internal links by default
  },
});
  • defaultStrategy: default is 'hover'; available strategies are hover (default), tap, viewport, load. [1][2]
  • prefetchAll: when true, all internal links are prefetched by default; you can opt out per-link with data-astro-prefetch="false". [1][2]

Per-link usage

Opt in:

<a href="/about" data-astro-prefetch>About</a>

Pick a strategy per link:

<a href="/about" data-astro-prefetch="tap">About</a>

Opt out (when prefetchAll: true):

<a href="/about" data-astro-prefetch="false">About</a>

(Works for internal links only.) [1]

Optional: client prerendering (experimental)

If you want prefetched pages to be prerendered in supported browsers (Speculation Rules API):

export default defineConfig({
  prefetch: { prefetchAll: true, defaultStrategy: 'viewport' },
  experimental: { clientPrerender: true },
});

[3]


🌐 Web query:

@astrojs/prefetch deprecation Astro 5 removal

💡 Result:

  • @astrojs/prefetch was deprecated in Astro v3.5.0 and replaced by Astro’s built-in prefetch feature. The docs explicitly say the integration “will eventually be removed entirely,” but they do not commit to a specific Astro 5.x removal version/date. [1] [2]
  • The npm package itself is also marked deprecated, instructing you to use the built-in prefetch option and follow the migration guide. [3]

What to do for Astro 5: remove @astrojs/prefetch, enable prefetch in astro.config.*, and use data-astro-prefetch / data-astro-prefetch="viewport" on links as needed. [1]

Sources: [1] [2] [3]


Replace deprecated @astrojs/prefetch with Astro 5's built-in prefetch configuration.

The @astrojs/prefetch integration was deprecated in Astro 3.5 and is marked deprecated on npm. Astro 5 includes built-in prefetching, making this dependency unnecessary.

Remove from package.json:

package.json diff
  "dependencies": {
-   "@astrojs/prefetch": "^0.4.1",
    "@astrojs/rss": "^4.0.15",
    "@astrojs/sitemap": "^3.7.0",
    "date-fns": "^4.1.0"
  }

Update astro.config.mjs:

astro.config.mjs diff
  import { defineConfig } from "astro/config";
- import prefetch from "@astrojs/prefetch";
  import sitemap from "@astrojs/sitemap";
  import tailwindcss from "@tailwindcss/vite";

  export default defineConfig({
    site: "https://t3.gg",
    image: {
      layout: "constrained",
      domains: ["t3.gg", "img.youtube.com"],
    },
    vite: {
      plugins: [tailwindcss()],
    },
    plugins: [],
-   integrations: [prefetch(), sitemap()],
+   integrations: [sitemap()],
+   prefetch: true,
    redirects: {
      "/links": "/",
      "/faq": "/",
    },
  });

Use data-astro-prefetch on individual links to control prefetching behavior:

<a href="/about" data-astro-prefetch>About</a>
<a href="/contact" data-astro-prefetch="tap">Contact</a>
🤖 Prompt for AI Agents
In `@package.json` around lines 24 - 27, Remove the deprecated "@astrojs/prefetch"
dependency from package.json and remove any related import/registration in
astro.config.mjs (look for occurrences of the string "@astrojs/prefetch" and the
integration variable/entry used to register it, e.g., an import or an
integrations array entry); then update astro.config.mjs to rely on Astro 5's
built-in prefetch (i.e., delete the prefetch integration entry and any config
options you added solely for that integration) and update your templates to use
data-astro-prefetch on links (e.g., check your .astro/.html files for <a>
elements to optionally add data-astro-prefetch or data-astro-prefetch="tap").
Ensure package.json no longer lists "@astrojs/prefetch" and run npm/yarn install
to update lockfile.

}
}
49 changes: 49 additions & 0 deletions src/components/BlogImage.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
import { Picture } from "astro:assets";
import type { ImageMetadata } from "astro";

interface Props {
src: ImageMetadata | string;
alt: string;
class?: string;
sizes?: string;
widths?: number[];
priority?: boolean;
}

const {
src,
alt,
class: className,
sizes = "(min-width: 768px) 768px, 100vw",
widths = [480, 768, 1024, 1280, 1600],
priority = false,
} = Astro.props;

const isLocalImage = typeof src !== "string";
const loading = priority ? "eager" : "lazy";
---

{
isLocalImage ? (
<Picture
src={src}
alt={alt}
sizes={sizes}
widths={widths}
formats={["avif", "webp"]}
loading={loading}
decoding="async"
class={className}
/>
) : (
<img
src={src}
alt={alt}
loading={loading}
decoding="async"
class={className}
/>
)
}

29 changes: 24 additions & 5 deletions src/components/BlogPostHeader.astro
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
---
import type { CollectionEntry } from "astro:content";
import { format } from "date-fns";
import BlogImage from "./BlogImage.astro";

export interface Props {
post: { title: string; date: string };
post: CollectionEntry<"posts">;
}

import { parseISO, format } from "date-fns";

const { post } = Astro.props;

const date = parseISO(post.date);
const date = post.data.date;
const formatted = format(date, "LLLL d, yyyy");
const coverImage = post.data.cover ?? post.data.imageURL;
const coverAlt = post.data.coverAlt ?? post.data.title;
---

<header class="not-prose mb-12 border-b border-white/10 pb-8">
<h1 class="mb-3 text-2xl font-medium tracking-tight text-text">
{post.title}
{post.data.title}
</h1>
<time datetime={date.toString()} class="text-sm text-muted">{formatted}</time>
Comment on lines +12 to 22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use ISO format for the <time> datetime attribute.
Line 22 uses date.toString(), which isn't guaranteed to be a valid datetime attribute value. Prefer ISO.

🛠️ Suggested fix
-  <time datetime={date.toString()} class="text-sm text-muted">{formatted}</time>
+  <time datetime={date.toISOString()} class="text-sm text-muted">{formatted}</time>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const date = post.data.date;
const formatted = format(date, "LLLL d, yyyy");
const coverImage = post.data.cover ?? post.data.imageURL;
const coverAlt = post.data.coverAlt ?? post.data.title;
---
<header class="not-prose mb-12 border-b border-white/10 pb-8">
<h1 class="mb-3 text-2xl font-medium tracking-tight text-text">
{post.title}
{post.data.title}
</h1>
<time datetime={date.toString()} class="text-sm text-muted">{formatted}</time>
const date = post.data.date;
const formatted = format(date, "LLLL d, yyyy");
const coverImage = post.data.cover ?? post.data.imageURL;
const coverAlt = post.data.coverAlt ?? post.data.title;
---
<header class="not-prose mb-12 border-b border-white/10 pb-8">
<h1 class="mb-3 text-2xl font-medium tracking-tight text-text">
{post.data.title}
</h1>
<time datetime={date.toISOString()} class="text-sm text-muted">{formatted}</time>
🤖 Prompt for AI Agents
In `@src/components/BlogPostHeader.astro` around lines 12 - 22, The time element
currently sets datetime using date.toString(), which can produce non-ISO values;
update the datetime attribute in BlogPostHeader's <time> to an ISO-formatted
string (e.g., use date.toISOString() or formatISO(date) if you're using
date-fns) instead of date.toString(), ensuring you reference the existing date
variable used to build formatted so the attribute contains a valid ISO 8601
datetime.

{post.data.archived ? (
<div class="mt-4 rounded-lg border border-amber-300/20 bg-amber-300/5 px-4 py-3 text-sm text-amber-100/90">
This post was archived manually by Theo. It is probably out of date or something.
</div>
) : null}
{coverImage ? (
<figure class="mt-8">
<BlogImage
src={coverImage}
alt={coverAlt}
priority={true}
class="w-full rounded-xl border border-white/10"
/>
</figure>
) : null}
</header>
14 changes: 7 additions & 7 deletions src/components/BlogPostPreview.astro
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
import type { CollectionEntry } from "astro:content";
import { format } from "date-fns";

export interface Props {
post: any;
post: CollectionEntry<"posts">;
}

import { getSlugFromPath } from "../utils/get-slug-from-path";
import { parseISO, format } from "date-fns";

const { post } = Astro.props;
const url = "/blog/post/" + getSlugFromPath(post.file);
const url = `/blog/post/${post.id}`;

const date = parseISO(post.frontmatter.date);
const date = post.data.date;
const formattedDate = format(date, "MMM d, yyyy");
---

Expand All @@ -18,7 +18,7 @@ const formattedDate = format(date, "MMM d, yyyy");
class="group relative flex items-baseline justify-between gap-4 py-3"
>
<span class="text-subtle transition-colors duration-200 group-hover:text-text">
{post.frontmatter.title}
{post.data.title}
</span>
<span class="shrink-0 text-sm text-muted">{formattedDate}</span>
<span
Expand Down
45 changes: 45 additions & 0 deletions src/content.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";

const posts = defineCollection({
loader: glob({
base: "./src/content/posts",
pattern: "**/index.md",
generateId: ({ entry, data }) => {
// Prefer an explicit, canonical slug so folders can move freely
// without breaking old links.
const explicitSlug =
typeof data.slug === "string" && data.slug.trim().length > 0
? data.slug.trim()
: undefined;
if (explicitSlug) return explicitSlug;

// Fall back to the entry path (minus the index file).
return entry.replace(/\/index\.md$/, "");
},
}),
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string().optional(),
date: z.coerce.date(),
updated: z.coerce.date().optional(),
tags: z.array(z.string()).optional(),
// Canonical slug used for stable URLs.
slug: z.string().optional(),
// Optional legacy slugs that should redirect to the canonical slug.
slugAliases: z.array(z.string()).optional(),
draft: z.boolean().default(false),
hidden: z.boolean().default(false),
archived: z.boolean().default(false),
readMore: z.boolean().optional(),
cover: image().optional(),
coverAlt: z.string().optional(),
imageURL: z.string().url().optional(),
canonicalUrl: z.string().url().optional(),
}),
});

export const collections = {
posts,
};
37 changes: 37 additions & 0 deletions src/content/posts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Blog Post Organization

This folder is organized for editing ergonomics without breaking links.

## Structure

- Active posts: `src/content/posts/active/<year>/<slug>/index.md`
- Archived posts: `src/content/posts/archive/<year>/<slug>/index.md`

## Canonical Slug (Important)

Each post should include a `slug` field in frontmatter:

```md
---
title: "My Post"
slug: "my-post"
date: "2026-02-02"
---
```

The `slug` is the canonical URL segment used at `/blog/post/<slug>`.
Because the slug is explicit, you can move posts between folders freely
without breaking old links.

## Optional Aliases

If you ever rename a slug, you can keep old URLs working:

```md
slug: "new-slug"
slugAliases:
- "old-slug"
```

Aliases will redirect to the canonical slug.

Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
---
title: "AirPods Max Review"
slug: "airpods-max"
date: "2020-12-22"
description: "An Audiophile who happens to love Apple comes to terms with this terrible product"
imageURL: "https://t3.gg/images/airpods-max/miles.jpg"
cover: ./images/miles.jpg
coverAlt: "Cat wearing AirPods Max"
readMore: true
---

![Cat wearing airpods max](https://t3.gg/images/airpods-max/miles.jpg)
![Cat wearing airpods max](./images/miles.jpg)

#### No Magic Here

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
title: "Hello World"
slug: "welcome"
date: "2020-12-21"
description: "Kicking off the blog and the stack behind it."
cover: ./images/lighthouse-scores.png
coverAlt: "Lighthouse performance scores"
---

This is long, long overdue.
Expand All @@ -11,7 +15,7 @@ This blog is also built on things I'm nerdy about. My personal site was previous

tl;dr on the tech - [Next.js](https://nextjs.org/) rewrite deployed on [Netlify](https://netlify.com)

It's nothing too fancy. Still pumped about the [lighthouse score tho](/images/lighthouse-scores.png)
It's nothing too fancy. Still pumped about the [lighthouse score tho](./images/lighthouse-scores.png)

I'll likely write more on the tech itself in the future - but for now, know I'm loving this stack and highly recommend it.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
title: "Quitting Your Dream Job (Twice)"
slug: "quitting"
date: "2021-09-02"
description: "Quitting is never easy. Building things can be. I'm leaving TTFM Labs to start a company."
readMore: true
Expand Down
Loading