Files for my website bwc9876.dev
at main 292 lines 7.6 kB view raw
1--- 2title: Welcome to my blog! 3date: 2023-10-15 4summary: How I built my blog 5cowsay: Hello World! 6--- 7 8import CowSay from "@components/blog/CowSay.astro"; 9 10## Hey there 11 12I decided to make this blog as a way to track my progress in learning new things. 13I hope you enjoy your stay! 14 15This first post is going into a bit of detail about how I made this blog. 16 17## Making The Basic Blog 18 19Astro has a wonderful feature called the [content framework](https://docs.astro.build/en/guides/content-collections/), an extremely powerful way to easily 20create many pages with simple markdown and some frontmatter. 21 22First thing you have to to do is create a folder called `content` in the src directory. 23I already had some content setup because of the projects parts of this site. 24 25Inside the content folder you place a `config.ts` which will contain the schemas 26for your content's frontmatter. I'll just focus on my blog posts for now. 27 28```ts 29const blogPostsCollection = defineCollection({ 30 schema: z.object({ 31 title: z.string(), 32 date: z.date(), 33 summary: z.string(), 34 cowsay: z.string() 35 }) 36}); 37``` 38 39This contains the metadata each blog post will need to have in order for my site 40to render it. 41 42Then, we export an object named `collections` which Astro will pick 43up and generate TS bindings for. 44 45```ts 46export const collections = { 47 posts: blogPostsCollection 48}; 49``` 50 51Now we can get to writing some content! Make a folder with the 52same name as the _key_ of the collection you want to write for. In this case, `posts`. 53 54Then create a markdown file and start writing! Here's a little excerpt 55of what [this page looks like](https://github.com/Bwc9876/portfolio-site/tree/main/src/content/posts/hello_world.mdx): 56 57```md 58--- 59title: Welcome to my blog! 60date: 2023-10-15 61summary: How I built my blog. 62cowsay: Hello World! 63--- 64 65## Hey there 66``` 67 68The frontmatter is the part between the `---` and `---`. This is where you put 69metadata for the post. The `cowsay` field is a special one that I made up. It 70changes what the cow says in the header of the page. I'll get to that later. 71 72Now that we have some content, we can start writing some code to render it! 73 74I start off by making a `blog` folder in the `src/pages` directory. This is where 75all my blog related pages will go. I then make a `index.astro` file which will 76be a directory of all posts on the site. 77 78```astro 79--- 80import Layout from "@layouts/Layout.astro"; 81import { getCollection } from "astro:content"; 82 83const blogEntries = await getCollection("posts"); 84--- 85 86<Layout title="The Cowsay - Ben C's Blog"> 87 <h1>The Cowsay - Ben C's Blog</h1> 88 <p>Here you'll find my blog posts, most recent first</p> 89 { 90 blogEntries.map((p, i) => ( 91 <> 92 {i === 0 && <hr />} 93 <hgroup> 94 <h2> 95 <a href={`/blog/posts/${p.id}`}>{p.data.title}</a> 96 </h2> 97 <h3> 98 {p.data.date.toLocaleDateString("en-us", { 99 weekday: "long", 100 year: "numeric", 101 month: "short", 102 day: "numeric" 103 })} 104 </h3> 105 </hgroup> 106 <p> 107 {p.data.summary}&nbsp;&nbsp;<a href={`/blog/posts/${p.id}`}>Read More</a> 108 </p> 109 <hr /> 110 </> 111 )) 112 } 113</Layout> 114``` 115 116Great! I'll probably fiddle with it in the future but it's a good start. Now we need to make a page for each post. 117To make my URLs look nice I'm going to create a subfolder within `blog` called `posts` and then place a `[...id].astro` in there. 118This will allow me to use `getStaticPaths()` to define the paths for each post. 119 120```astro 121--- 122import Layout from "@layouts/Layout.astro"; 123import { CollectionEntry, getCollection } from "astro:content"; 124export const getStaticPaths = async () => { 125 const posts = await getCollection("posts"); 126 return posts.map((entry) => ({ 127 params: { slug: entry.id }, 128 props: { entry } 129 })); 130}; 131 132const { entry } = Astro.props as { entry: CollectionEntry<"posts"> }; 133const { Content } = await entry.render(); 134--- 135 136<Layout title={entry.data.title} description={entry.data.summary}> 137 <h1>{entry.data.title}</h1> 138 <Content /> 139</Layout> 140 141<style is:global> 142 main img { 143 border: solid 1px var(--text) !important; 144 border-radius: 5px; 145 } 146</style> 147``` 148 149Amazing! I'll spare you the image this time since... you're... look at it. But now we have a blog post page! 150Now anytime I want to make a new post I just have to make a new markdown file and it'll be rendered on the site. 151 152## An Outline 153 154Now that we have a basic blog, I want to add a few more features to it. First I want to add the ability to see all headers in a post. 155This should be pretty easy to do. Astro automatically parses all the headers for us and lets us access them in the `entry` object. 156 157First we need to grab the headings from when we rendered the page: 158 159```js 160const { Content, headings } = await entry.render(); 161``` 162 163Then we need to map those to HTML 164 165```astro 166<div class="toc"> 167 <!-- Extra div so we can make it sticky --> 168 <div> 169 <span>On This Page</span> 170 <ul> 171 { 172 headings.map((h) => ( 173 <li> 174 <a href={`#${h.id}`}>{h.text}</a> 175 </li> 176 )) 177 } 178 </ul> 179 </div> 180</div> 181``` 182 183Finally some simple styles and layout 184 185```astro 186<style> 187 /** Wrapper is going around everything to make the 188 table of contents appear on the right of the page **/ 189 div.wrapper { 190 display: flex; 191 flex-direction: row; 192 gap: 4rem; 193 } 194 195 div.toc { 196 width: 100%; 197 } 198 199 div.toc div { 200 top: 5rem; 201 margin-top: 1rem; 202 position: sticky; 203 } 204 205 div.toc ul li { 206 list-style-type: none; 207 } 208</style> 209``` 210 211Finally, I want to make sure on smaller screen sizes the table of contents appears at the top rather than the side so it doesn't take up too much space. 212 213```css 214div.wrapper { 215 display: flex; 216 flex-direction: column-reverse; 217 gap: 1rem; 218} 219 220@media (min-width: 1200px) { 221 div.wrapper { 222 flex-direction: row; 223 gap: 4rem; 224 } 225} 226``` 227 228## The Cowsay 229 230Now for the fun part. I want to make a little cow that says something in the header of each post. I'm going to use the `cowsay` field in the frontmatter to do this. 231I'll also provide a CowSay component that will render the cow and the text. This way I can use it MDX for admonitions. 232 233First I need to make a component that will render the cow. I'm going to use the [cowsay](https://www.npmjs.com/package/cowsay) package to do this. 234 235```astro 236--- 237import * as cowsay from "cowsay"; 238 239type Props = { 240 color?: "warn" | "info"; 241} & cowsay.IOptions; 242 243const { color, ...cowOptions } = Astro.props; 244 245const cowText = cowsay.say(cowOptions); 246--- 247 248<pre class={color}> 249{cowText} 250</pre> 251 252<style> 253 pre { 254 padding: 1rem; 255 } 256 257 pre.warn { 258 color: yellow; 259 background-color: rgb(25, 25, 0); 260 } 261 262 pre.info { 263 color: cyan; 264 background-color: rgb(0, 25, 25); 265 } 266</style> 267``` 268 269Now I can link it up in my blog post page! 270 271```astro 272<CowSay text={entry.data.cowsay} /> 273``` 274 275Et voila! A cow that says something in the header of each post! I'll probably make it a bit more fancy in the future but for now it's good enough. 276 277I can also use it for admonitions in MDX! 278 279```astro 280<CowSay color="warn" e="><" text="Warning!" /> 281<CowSay color="info" e="^^" T="U" text="Info!" /> 282``` 283 284<CowSay color="warn" e="><" text="Warning!" /> 285<CowSay color="info" e="^^" T="U" text="Info!" /> 286 287## Conclusion 288 289I'm really happy with how this blog turned out. I'm going to keep working on it and adding new features as I go. 290I'm also going to try and write more posts in the future. I hope you enjoyed this one! 291 292<CowSay e="^^" text="Adiós!" />