Getting Started with Folio

This post walks through how Folio is built — how posts are written, what the frontmatter schema looks like, and how SEO and social sharing tags are generated automatically for every post.

Writing a Post

Every blog post is a plain Markdown file stored in content/blog/. The filename becomes the URL slug:

content/blog/my-post-title.md  →  /blog/my-post-title

At the top of each file is a frontmatter block — a YAML section wrapped in --- that provides metadata about the post.

Frontmatter Schema

Here is the full schema this blog supports:

---
title: "My Post Title"        # required — shown in the page heading and browser tab
date: 2025-01-15              # required — used for sorting and displayed on the post
summary: "Short description"  # required — shown on the blog listing card
tags: ["Vue", "Nuxt"]         # optional — displayed as pills at the bottom of the post
author: "Your Name"           # optional — defaults to "Unknown"
status: "published"           # "draft" | "published" — drafts are hidden from the listing
seo:
  description: "Custom text for search engines and social previews"
  keywords: ["keyword1", "keyword2"]
  og:image: "https://example.com/cover.jpg"
---

All fields are validated at build time using Zod. If a required field is missing, the build will fail with a clear error.

Draft vs Published

Setting status: "draft" hides the post from the blog listing and sitemap. It is still accessible by direct URL during development, making it easy to preview before publishing.

SEO and Open Graph Tags

Every page on this blog sets meta tags automatically using Nuxt's built-in useSeoMeta() composable. On post pages, the tags are populated from the frontmatter:

useSeoMeta({
  title: () => post.value?.title,
  description: () => post.value?.seo?.description || post.value?.summary,
  // Open Graph — controls previews on LinkedIn, Slack, iMessage, etc.
  ogTitle: () => post.value?.title,
  ogDescription: () => post.value?.seo?.description || post.value?.summary,
  ogImage: () => post.value?.seo?.["og:image"] || "",
  ogType: "article",
  // Twitter / X Card
  twitterCard: "summary_large_image",
  twitterTitle: () => post.value?.title,
  twitterDescription: () => post.value?.seo?.description || post.value?.summary,
});

Fallback chain for description

The seo.description field is optional. When it is absent, useSeoMeta falls back to the post's summary. This means every post gets a meaningful social preview as long as summary is set — even without a dedicated seo block.

og:image

If you provide an og:image URL in the frontmatter, it will appear as the preview image when the post is shared on social platforms. The URL must be absolute (e.g. https://example.com/cover.jpg) — relative paths are not fetched by social crawlers.

Markdown Features

Standard Markdown is fully supported inside the post body:

Headings

## H2 heading
### H3 heading (also appears in the table of contents)

Code Blocks

Fenced code blocks with language hints get syntax highlighting automatically:

```ts
const greeting = (name: string) => `Hello, ${name}!`
```

Lists, Bold, Italic

- item one
- **bold text**
- _italic text_
[Link text](https://example.com)

Sitemap

Published posts are included in /sitemap.xml automatically. A server endpoint queries all posts with status: "published" and passes them to @nuxtjs/sitemap, which pre-renders the sitemap file at build time. Draft posts are excluded.