Fix BlogPost layout frontmatter and stabilize blog rendering

This commit is contained in:
scadmin
2025-12-16 20:01:14 +00:00
parent 0782fe7c82
commit bcf5724d38
7 changed files with 141 additions and 205 deletions

View File

@@ -1,31 +0,0 @@
---
title: 'Using MDX'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jun 01 2024'
heroImage: '../../assets/blog-placeholder-5.jpg'
---
This theme comes with the [@astrojs/mdx](https://docs.astro.build/en/guides/integrations-guide/mdx/) integration installed and configured in your `astro.config.mjs` config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file.
## Why MDX?
MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to [mix JavaScript and UI Components into your Markdown content](https://docs.astro.build/en/guides/markdown-content/#mdx-features) for things like interactive charts or alerts.
If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze.
## Example
Here is how you import and use a UI component inside of MDX.
When you open this page in the browser, you should see the clickable button below.
import HeaderLink from '../../components/HeaderLink.astro';
<HeaderLink href="#" onclick="alert('clicked!')">
Embedded component in MDX
</HeaderLink>
## More Links
- [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx)
- [Astro Usage Documentation](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages)
- **Note:** [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.

View File

@@ -2,20 +2,15 @@ import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string().optional(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
draft: z.boolean().optional().default(false),
// Optional if you use hero images in your layout:
heroImage: z
.object({
src: z.string(),
alt: z.string().optional(),
})
.optional(),
}),
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string().optional(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
draft: z.boolean().default(false),
heroImage: image().optional(),
}),
});
export const collections = { blog };

View File

@@ -1,86 +1,88 @@
---
import { Image } from 'astro:assets';
import type { CollectionEntry } from 'astro:content';
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro';
import Header from '../components/Header.astro';
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
import BaseHead from "../components/BaseHead.astro";
import Footer from "../components/Footer.astro";
import FormattedDate from "../components/FormattedDate.astro";
import Header from "../components/Header.astro";
type Props = CollectionEntry<'blog'>['data'];
type Props = CollectionEntry<"blog">["data"];
const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
const { title, description, pubDate, updatedDate, heroImage } = Astro.props as Props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<style>
main {
width: calc(100% - 2em);
max-width: 100%;
margin: 0;
}
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 0 auto;
border-radius: 12px;
box-shadow: var(--box-shadow);
}
.prose {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 1em;
color: rgb(var(--gray-dark));
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: rgb(var(--gray));
}
.last-updated-on {
font-style: italic;
}
</style>
</head>
<head>
<BaseHead title={title} description={description} />
<style>
main {
width: calc(100% - 2em);
max-width: 100%;
margin: 0;
}
.hero-image {
width: 100%;
}
.hero-image :global(img) {
display: block;
margin: 0 auto;
border-radius: 12px;
box-shadow: var(--box-shadow);
}
.prose {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 1em;
color: rgb(var(--gray-dark));
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: rgb(var(--gray));
}
.last-updated-on {
font-style: italic;
}
</style>
</head>
<body>
<Header />
<main>
<article>
<div class="hero-image">
{heroImage && <Image width={1020} height={510} src={heroImage} alt="" />}
</div>
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={pubDate} />
{
updatedDate && (
<div class="last-updated-on">
Last updated on <FormattedDate date={updatedDate} />
</div>
)
}
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
<body>
<Header />
<main>
<article>
{heroImage && (
<div class="hero-image">
<Image src={heroImage} width={1020} height={510} alt={title} />
</div>
)}
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={pubDate} />
{updatedDate && (
<div class="last-updated-on">
Last updated on <FormattedDate date={updatedDate} />
</div>
)}
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>

View File

@@ -1,24 +1,24 @@
---
import { type CollectionEntry, getCollection, render } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
import { type CollectionEntry, getCollection, render } from "astro:content";
import BlogPost from "../../layouts/BlogPost.astro";
export async function getStaticPaths() {
const posts = await getCollection('blog');
const posts = await getCollection("blog");
return posts
.filter((post) => !(post.data as { draft?: boolean }).draft)
.map((post) => ({
params: { slug: post.id },
props: post,
}));
return posts
.filter((p) => !(((p.data as any).draft) ?? false))
.map((post) => ({
params: { slug: post.id },
props: post,
}));
}
type Props = CollectionEntry<'blog'>;
type Props = CollectionEntry<"blog">;
const post = Astro.props as Props;
const { Content } = await render(post);
---
<BlogPost {...post.data}>
<Content />
<Content />
</BlogPost>

View File

@@ -7,54 +7,33 @@ import { SITE_DESCRIPTION, SITE_TITLE } from '../../consts';
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter((p) => !(p.data as { draft?: boolean }).draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
.filter((p) => !(((p.data as any).draft) ?? false))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={`${SITE_TITLE} | Blog`} description={SITE_DESCRIPTION} />
</head>
<body>
<Header />
<main>
<h1>Sean's Cloud!</h1>
<h3>Infrastructure • Cloud • Systems Thinking • Leadership</h3>
<head>
<BaseHead title="Blog | Sean's Cloud" description="Posts, lab notes, and lessons learned." />
</head>
<body>
<Header />
<main>
<h1>Blog</h1>
<p>Lab notes, guides, and lessons learned.</p>
<p>
seans.cloud is an evolving space focused on building, testing, and refining modern IT and cloud practices with an eye toward whats next. It serves as a foundation for future projects in cloud architecture, automation, security, and operational excellence—along with the leadership thinking that supports sustainable systems.
</p>
<p>
As this site grows, it will become a place to share practical insights, real-world experiments, and lessons learned from working with infrastructure at scale. Future content may include technical guides, architecture patterns, tooling evaluations, and perspectives on how technology, process, and people intersect to create resilient organizations.
</p>
<p>On this site youll find:</p>
<ul>
<li>Technical experiments and lab notes</li>
<li>Cloud and infrastructure architecture thinking</li>
<li>Reflections on reliability, security, and process</li>
<li>Lessons learned from real-world systems</li>
</ul>
<hr />
<h2>Latest Posts</h2>
{posts.length === 0 ? (
<p>No posts yet. Check back soon.</p>
) : (
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.id}`}>{post.data.title}</a>
<small> — {post.data.pubDate.toLocaleDateString()}</small>
{post.data.description ? <p>{post.data.description}</p> : null}
</li>
))}
</ul>
)}
</main>
<Footer />
</body>
</html>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.id}/`}>{post.data.title}</a>
<div>
<small>{post.data.pubDate.toLocaleDateString()}</small>
{post.data.description ? <p>{post.data.description}</p> : null}
</div>
</li>
))}
</ul>
</main>
<Footer />
</body>
</html>

View File

@@ -6,10 +6,10 @@ import { SITE_DESCRIPTION, SITE_TITLE } from '../consts';
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter((p) => !(p.data as { draft?: boolean }).draft)
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
.slice(0, 5);
const latest = (await getCollection('blog'))
.filter((p) => !(((p.data as any).draft) ?? false))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
.slice(0, 5);
---
<!doctype html>
@@ -41,27 +41,20 @@ const posts = (await getCollection('blog'))
<hr />
<h2>Latest Posts</h2>
{posts.length === 0 ? (
<p>No posts yet. Check back soon.</p>
{latest.length === 0 ? (
<p>No posts yet.</p>
) : (
<>
<ul>
{posts.map((post) => (
<li>
<a href={`/blog/${post.id}`}>{post.data.title}</a>
<small> — {post.data.pubDate.toLocaleDateString()}</small>
{post.data.description ? <p>{post.data.description}</p> : null}
</li>
))}
</ul>
<p>
<a href="/blog">View all posts →</a>
</p>
</>
<ul>
{latest.map((post) => (
<li>
<a href={`/blog/${post.id}/`}>{post.data.title}</a>
{post.data.description ? <p>{post.data.description}</p> : null}
</li>
))}
</ul>
)}
</main>
<Footer />
</body>
</html>

View File

@@ -1,8 +1,6 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"strictNullChecks": true
"baseUrl": "."
}
}