Fix BlogPost layout frontmatter and stabilize blog rendering
This commit is contained in:
@@ -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.
|
|
||||||
@@ -2,20 +2,15 @@ import { defineCollection, z } from "astro:content";
|
|||||||
|
|
||||||
const blog = defineCollection({
|
const blog = defineCollection({
|
||||||
type: "content",
|
type: "content",
|
||||||
schema: z.object({
|
schema: ({ image }) =>
|
||||||
title: z.string(),
|
z.object({
|
||||||
description: z.string().optional(),
|
title: z.string(),
|
||||||
pubDate: z.coerce.date(),
|
description: z.string().optional(),
|
||||||
updatedDate: z.coerce.date().optional(),
|
pubDate: z.coerce.date(),
|
||||||
draft: z.boolean().optional().default(false),
|
updatedDate: z.coerce.date().optional(),
|
||||||
// Optional if you use hero images in your layout:
|
draft: z.boolean().default(false),
|
||||||
heroImage: z
|
heroImage: image().optional(),
|
||||||
.object({
|
}),
|
||||||
src: z.string(),
|
|
||||||
alt: z.string().optional(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const collections = { blog };
|
export const collections = { blog };
|
||||||
|
|||||||
@@ -1,86 +1,88 @@
|
|||||||
---
|
---
|
||||||
import { Image } from 'astro:assets';
|
import { Image } from "astro:assets";
|
||||||
import type { CollectionEntry } from 'astro:content';
|
import type { CollectionEntry } from "astro:content";
|
||||||
import BaseHead from '../components/BaseHead.astro';
|
import BaseHead from "../components/BaseHead.astro";
|
||||||
import Footer from '../components/Footer.astro';
|
import Footer from "../components/Footer.astro";
|
||||||
import FormattedDate from '../components/FormattedDate.astro';
|
import FormattedDate from "../components/FormattedDate.astro";
|
||||||
import Header from '../components/Header.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">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<BaseHead title={title} description={description} />
|
<BaseHead title={title} description={description} />
|
||||||
<style>
|
<style>
|
||||||
main {
|
main {
|
||||||
width: calc(100% - 2em);
|
width: calc(100% - 2em);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.hero-image {
|
.hero-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.hero-image img {
|
.hero-image :global(img) {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: var(--box-shadow);
|
box-shadow: var(--box-shadow);
|
||||||
}
|
}
|
||||||
.prose {
|
.prose {
|
||||||
width: 720px;
|
width: 720px;
|
||||||
max-width: calc(100% - 2em);
|
max-width: calc(100% - 2em);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
color: rgb(var(--gray-dark));
|
color: rgb(var(--gray-dark));
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.title h1 {
|
.title h1 {
|
||||||
margin: 0 0 0.5em 0;
|
margin: 0 0 0.5em 0;
|
||||||
}
|
}
|
||||||
.date {
|
.date {
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
color: rgb(var(--gray));
|
color: rgb(var(--gray));
|
||||||
}
|
}
|
||||||
.last-updated-on {
|
.last-updated-on {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<Header />
|
<Header />
|
||||||
<main>
|
<main>
|
||||||
<article>
|
<article>
|
||||||
<div class="hero-image">
|
{heroImage && (
|
||||||
{heroImage && <Image width={1020} height={510} src={heroImage} alt="" />}
|
<div class="hero-image">
|
||||||
</div>
|
<Image src={heroImage} width={1020} height={510} alt={title} />
|
||||||
<div class="prose">
|
</div>
|
||||||
<div class="title">
|
)}
|
||||||
<div class="date">
|
|
||||||
<FormattedDate date={pubDate} />
|
<div class="prose">
|
||||||
{
|
<div class="title">
|
||||||
updatedDate && (
|
<div class="date">
|
||||||
<div class="last-updated-on">
|
<FormattedDate date={pubDate} />
|
||||||
Last updated on <FormattedDate date={updatedDate} />
|
{updatedDate && (
|
||||||
</div>
|
<div class="last-updated-on">
|
||||||
)
|
Last updated on <FormattedDate date={updatedDate} />
|
||||||
}
|
</div>
|
||||||
</div>
|
)}
|
||||||
<h1>{title}</h1>
|
</div>
|
||||||
<hr />
|
<h1>{title}</h1>
|
||||||
</div>
|
<hr />
|
||||||
<slot />
|
</div>
|
||||||
</div>
|
|
||||||
</article>
|
<slot />
|
||||||
</main>
|
</div>
|
||||||
<Footer />
|
</article>
|
||||||
</body>
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
---
|
---
|
||||||
import { type CollectionEntry, getCollection, render } from 'astro:content';
|
import { type CollectionEntry, getCollection, render } from "astro:content";
|
||||||
import BlogPost from '../../layouts/BlogPost.astro';
|
import BlogPost from "../../layouts/BlogPost.astro";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = await getCollection('blog');
|
const posts = await getCollection("blog");
|
||||||
|
|
||||||
return posts
|
return posts
|
||||||
.filter((post) => !(post.data as { draft?: boolean }).draft)
|
.filter((p) => !(((p.data as any).draft) ?? false))
|
||||||
.map((post) => ({
|
.map((post) => ({
|
||||||
params: { slug: post.id },
|
params: { slug: post.id },
|
||||||
props: post,
|
props: post,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = CollectionEntry<'blog'>;
|
type Props = CollectionEntry<"blog">;
|
||||||
|
|
||||||
const post = Astro.props as Props;
|
const post = Astro.props as Props;
|
||||||
const { Content } = await render(post);
|
const { Content } = await render(post);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BlogPost {...post.data}>
|
<BlogPost {...post.data}>
|
||||||
<Content />
|
<Content />
|
||||||
</BlogPost>
|
</BlogPost>
|
||||||
|
|||||||
@@ -7,54 +7,33 @@ import { SITE_DESCRIPTION, SITE_TITLE } from '../../consts';
|
|||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const posts = (await getCollection('blog'))
|
const posts = (await getCollection('blog'))
|
||||||
.filter((p) => !(p.data as { draft?: boolean }).draft)
|
.filter((p) => !(((p.data as any).draft) ?? false))
|
||||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<BaseHead title={`${SITE_TITLE} | Blog`} description={SITE_DESCRIPTION} />
|
<BaseHead title="Blog | Sean's Cloud" description="Posts, lab notes, and lessons learned." />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Header />
|
<Header />
|
||||||
<main>
|
<main>
|
||||||
<h1>Sean's Cloud!</h1>
|
<h1>Blog</h1>
|
||||||
<h3>Infrastructure • Cloud • Systems Thinking • Leadership</h3>
|
<p>Lab notes, guides, and lessons learned.</p>
|
||||||
|
|
||||||
<p>
|
<ul>
|
||||||
seans.cloud is an evolving space focused on building, testing, and refining modern IT and cloud practices with an eye toward what’s 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.
|
{posts.map((post) => (
|
||||||
</p>
|
<li>
|
||||||
<p>
|
<a href={`/blog/${post.id}/`}>{post.data.title}</a>
|
||||||
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.
|
<div>
|
||||||
</p>
|
<small>{post.data.pubDate.toLocaleDateString()}</small>
|
||||||
|
{post.data.description ? <p>{post.data.description}</p> : null}
|
||||||
<p>On this site you’ll find:</p>
|
</div>
|
||||||
<ul>
|
</li>
|
||||||
<li>Technical experiments and lab notes</li>
|
))}
|
||||||
<li>Cloud and infrastructure architecture thinking</li>
|
</ul>
|
||||||
<li>Reflections on reliability, security, and process</li>
|
</main>
|
||||||
<li>Lessons learned from real-world systems</li>
|
<Footer />
|
||||||
</ul>
|
</body>
|
||||||
|
</html>
|
||||||
<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>
|
|
||||||
@@ -6,10 +6,10 @@ import { SITE_DESCRIPTION, SITE_TITLE } from '../consts';
|
|||||||
|
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
const posts = (await getCollection('blog'))
|
const latest = (await getCollection('blog'))
|
||||||
.filter((p) => !(p.data as { draft?: boolean }).draft)
|
.filter((p) => !(((p.data as any).draft) ?? false))
|
||||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
|
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
|
||||||
.slice(0, 5);
|
.slice(0, 5);
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -41,27 +41,20 @@ const posts = (await getCollection('blog'))
|
|||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<h2>Latest Posts</h2>
|
<h2>Latest Posts</h2>
|
||||||
|
{latest.length === 0 ? (
|
||||||
{posts.length === 0 ? (
|
<p>No posts yet.</p>
|
||||||
<p>No posts yet. Check back soon.</p>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<ul>
|
||||||
<ul>
|
{latest.map((post) => (
|
||||||
{posts.map((post) => (
|
<li>
|
||||||
<li>
|
<a href={`/blog/${post.id}/`}>{post.data.title}</a>
|
||||||
<a href={`/blog/${post.id}`}>{post.data.title}</a>
|
{post.data.description ? <p>{post.data.description}</p> : null}
|
||||||
<small> — {post.data.pubDate.toLocaleDateString()}</small>
|
</li>
|
||||||
{post.data.description ? <p>{post.data.description}</p> : null}
|
))}
|
||||||
</li>
|
</ul>
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="/blog">View all posts →</a>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"include": [".astro/types.d.ts", "**/*"],
|
|
||||||
"exclude": ["dist"],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strictNullChecks": true
|
"baseUrl": "."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user