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({
|
||||
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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 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.
|
||||
</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 you’ll 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"],
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user