Getting Started with Nextfolio

Start with the essentials

Nextfolio gives you a focused writing setup:

  • content-driven posts with frontmatter validation
  • readable typography with Tailwind Typography
  • theme variables for light and dark mode

Write your first post

Create a new file in src/content/blog, add frontmatter, and write Markdown or MDX.

---
title: My First Post
desc: Notes from my first week.
pubDate: 2026-04-30
tags: [notes]
---

Code Blocks

Basic Code Block

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example HTML5 Document</title>
</head>
<body>
<p>Test</p>
</body>
</html>

Filename Tabs

Add a title to show a filename tab above the code block:

config.js
const siteConfig = {
name: 'My Blog',
url: 'https://example.com',
lang: 'en',
}
export default siteConfig
styles/global.css
:root {
--accent: #2337ff;
--gray: 96, 115, 159;
}
body {
font-family: system-ui, sans-serif;
color: rgb(var(--gray));
}

Line Numbers

Enable line numbers per block with showLineNumbers:

utils.ts
export function fibonacci(n: number): number {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
}
export function factorial(n: number): number {
if (n <= 1) return 1
return n * factorial(n - 1)
}
console.log(fibonacci(10)) // 55
console.log(factorial(5)) // 120

You can also set a custom starting number:

example.py
def greet(name: str) -> str:
return f"Hello, {name}!"
for i in range(3):
print(greet(f"User {i}"))

Highlighted Lines

Highlight specific lines with {lineNumbers}:

highlight-example.js
const items = ['apple', 'banana', 'cherry']
const filtered = items.filter(item => item.length > 5) // highlighted
console.log(filtered) // ['banana', 'cherry']
const sorted = filtered.sort()
console.log(sorted)

Insertions and Deletions (Diffs)

Show additions and removals with ins and del:

diff-example.js
function getGreeting(name) {
return 'Hello, ' + name + '!'
const greeting = `Hello, ${name}!`
return greeting
}

You can also use the mark marker for neutral highlights:

marked-example.ts
export function loadData() {
const data = fetch('/api/data')
// This function is critical to the app
return data.json()
}

Word Wrap

Long lines wrap by default. Disable per block with wrap=false:

terminal.sh
echo "This is a very long command that demonstrates how word wrapping works in Expressive Code blocks when the content exceeds the available width"

Collapsible Sections

Collapse repetitive or less important code with collapse:

collapsible-example.js
import { createApp } from 'vue'
import App from './App.vue'
// ---vvv--- Collapsed boilerplate ---vvv---
11 collapsed lines
const app = createApp(App)
app.use(router)
app.use(store)
app.use(i18n)
app.directive('focus', {})
app.component('GlobalComponent', {})
app.mixin({})
app.config.errorHandler = () => {}
app.config.warnHandler = () => {}
// ---^^^--- End collapsed section ---^^^---
// Your actual app logic starts here
app.mount('#app')

Terminal Blocks

Shell commands automatically get a terminal-style frame:

Terminal window
npm create astro@latest -- --template blog
cd my-blog
npm install
npm run dev

Combined Features

You can combine multiple features in a single block:

src/components/Button.tsx
import type { ReactNode } from 'react'
interface ButtonProps {
children: ReactNode
variant?: 'primary' | 'secondary'
type?: 'button' | 'submit'
disabled?: boolean
}
export function Button({ children, variant = 'primary', ...props }: ButtonProps) {
const className = `btn btn-${variant}`
return (
<button className={className} {...props}>
{children}
</button>
)
}

Callouts

Use the Callout component to highlight important information in your posts: