Markdown blog
Bu sayfada
Statik sitenin en tipik kullanımı: bir klasördeki markdown dosyaları, yazı başına bir statik sayfa, bir RSS akışı ve sıfır istemci tarafı JavaScript. Tam kaynak examples/blog içinde.
Elinize geçenler
- Yazıları en yeniden başlayarak listeleyen bir ana sayfa
/blog/[slug]—generateStaticParamsaracılığıyla markdown dosyası başına bir statik HTML sayfasırss.xml— sitelo tarafındanrssyapılandırmasından üretilirsitemap.xml—siteayarlanarak açılır- Sıfır JS yayımlanır — markdown ayrıştırma derleme sırasında Node’da olur
Proje düzeni
my-blog/
sitelo.config.js
content/
hello-world.md # posts: frontmatter + markdown
why-static.md
src/
lib/
posts.js # content/ dizinini okur, frontmatter’ı ayrıştırır, markdown’ı işler
index.ht.js # / — yazı listesi
blog/
[slug].ht.js # /blog/:slug — yazı başına bir sayfa
css/
styles.cssexport default {
site: 'https://example.com',
rss: {
site: 'https://example.com',
title: 'Blogum',
description: 'Son yazılar',
routePrefix: '/blog', // her /blog/* sayfası bir akış ögesi olur
},
}1. Yazıları markdown olarak yazın
Yazılar content/ içinde yaşar — src/ dışında, böylece sitelo onları hiçbir zaman sayfa ya da varlık saymaz. Frontmatter düz key: value satırlarıdır:
---
title: Merhaba, dünya
date: 2026-08-01
description: Kaçınılmaz ilk yazı.
---
Bu blog, statik HTML’e işlenen bir markdown dosyaları klasörüdür.2. Onları Node’da okuyun ve işleyin
Yalnızca sunucuya ait küçük bir modül klasörü okur, frontmatter’ı ayrıştırır ve markdown’ı marked ile işler. HTML içinde hiçbir şey bu modüle başvurmadığından tarayıcıya hiç gönderilmez.
import { readdir, readFile } from 'node:fs/promises'
import { marked } from 'marked'
// Yazılar src/ dışında durur, böylece hiçbir zaman sayfa ya da varlık sayılmazlar.
const CONTENT_DIR = new URL('../../content/', import.meta.url)
/** Minik frontmatter ayrıştırıcı — --- çitleri arasındaki `anahtar: değer` satırları. */
function parseFrontmatter(raw) {
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(raw)
if (!match) return { meta: {}, body: raw }
const meta = {}
for (const line of match[1].split('\n')) {
const colon = line.indexOf(':')
if (colon === -1) continue
meta[line.slice(0, colon).trim()] = line.slice(colon + 1).trim()
}
return { meta, body: raw.slice(match[0].length) }
}
/** Tüm yazılar, en yeniden başlayarak. Yalnızca derleme/geliştirme sırasında Node’da çalışır. */
export async function getPosts() {
const files = (await readdir(CONTENT_DIR)).filter((file) =>
file.endsWith('.md'),
)
const posts = await Promise.all(
files.map(async (file) => {
const raw = await readFile(new URL(file, CONTENT_DIR), 'utf8')
const { meta, body } = parseFrontmatter(raw)
return {
slug: file.replace(/\.md$/, ''),
title: meta.title ?? file,
date: meta.date ?? '1970-01-01',
description: meta.description ?? '',
html: marked.parse(body),
}
}),
)
return posts.sort((a, b) => b.date.localeCompare(a.date))
}
export async function getPost(slug) {
const posts = await getPosts()
return posts.find((post) => post.slug === slug) ?? null
}3. Yazıları ana sayfada listeleyin
import { getPosts } from './lib/posts.js'
export async function data() {
return { posts: await getPosts() }
}
export default ({ data }) => `
<html lang="tr">
<head>
<title>Blogum</title>
<link rel="stylesheet" href="/css/styles.css">
<link rel="alternate" type="application/rss+xml" title="Blogum" href="/rss.xml">
</head>
<body>
<h1>Blogum</h1>
<ul class="posts">
${data.posts
.map(
(post) => `
<li>
<a href="/blog/${post.slug}">${post.title}</a>
<time datetime="${post.date}">${post.date}</time>
<p>${post.description}</p>
</li>`,
)
.join('')}
</ul>
<p><a href="/rss.xml">RSS akışı</a></p>
</body>
</html>
`import {
html, head, title, link, body, h1, ul, li, a, time, p,
} from 'javascript-to-html'
import { getPosts } from './lib/posts.js'
export async function data() {
return { posts: await getPosts() }
}
export default ({ data }) =>
html({ lang: 'tr' },
head(
title('Blogum'),
link({ rel: 'stylesheet', href: '/styles.css' }),
link({
rel: 'alternate',
type: 'application/rss+xml',
title: 'Blogum',
href: '/rss.xml',
}),
),
body(
h1('Blogum'),
ul({ class: 'posts' },
...data.posts.map((post) =>
li(
a({ href: `/blog/${post.slug}` }, post.title),
time({ datetime: post.date }, post.date),
p(post.description),
),
),
),
p(a({ href: '/rss.xml' }, 'RSS akışı')),
),
)import { getPosts } from './lib/posts.js'
export async function data() {
return { posts: await getPosts() }
}
export default function Home({ data }) {
return (
<html lang="tr">
<head>
<title>Blogum</title>
<link rel="stylesheet" href="/css/styles.css" />
<link
rel="alternate"
type="application/rss+xml"
title="Blogum"
href="/rss.xml"
/>
</head>
<body>
<h1>Blogum</h1>
<ul className="posts">
{data.posts.map((post) => (
<li key={post.slug}>
<a href={`/blog/${post.slug}`}>{post.title}</a>
<time dateTime={post.date}>{post.date}</time>
<p>{post.description}</p>
</li>
))}
</ul>
<p><a href="/rss.xml">RSS akışı</a></p>
</body>
</html>
)
}4. Yazı başına bir statik sayfa
generateStaticParams derleme sırasında her slug’ı döndürür; data() her sayfa için eşleşen yazıyı yükler.
import { getPost, getPosts } from '../lib/posts.js'
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export async function data({ params }) {
const post = await getPost(params.slug)
if (!post) throw new Error(`Yazı bulunamadı: ${params.slug}`)
return { post }
}
export default ({ data }) => {
const { post } = data
return `
<html lang="tr">
<head>
<title>${post.title} — Blogum</title>
<meta name="description" content="${post.description}">
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<article>
<p><a href="/">← Tüm yazılar</a></p>
<h1>${post.title}</h1>
<time datetime="${post.date}">${post.date}</time>
${post.html}
</article>
</body>
</html>
`
}import {
html, head, title, meta, link, body, article, p, a, h1, time,
} from 'javascript-to-html'
import { getPost, getPosts } from '../lib/posts.js'
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export async function data({ params }) {
const post = await getPost(params.slug)
if (!post) throw new Error(`Yazı bulunamadı: ${params.slug}`)
return { post }
}
export default ({ data }) => {
const { post } = data
return html({ lang: 'tr' },
head(
title(`${post.title} — Blogum`),
meta({ name: 'description', content: post.description }),
link({ rel: 'stylesheet', href: '/styles.css' }),
),
body(
article(
p(a({ href: '/' }, '← Tüm yazılar')),
h1(post.title),
time({ datetime: post.date }, post.date),
post.html,
),
),
)
}import { getPost, getPosts } from '../lib/posts.js'
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export async function data({ params }) {
const post = await getPost(params.slug)
if (!post) throw new Error(`Yazı bulunamadı: ${params.slug}`)
return { post }
}
export default function Post({ data }) {
const { post } = data
return (
<html lang="tr">
<head>
<title>{post.title} — Blogum</title>
<meta name="description" content={post.description} />
<link rel="stylesheet" href="/css/styles.css" />
</head>
<body>
<article>
<p><a href="/">← Tüm yazılar</a></p>
<h1>{post.title}</h1>
<time datetime={post.date}>{post.date}</time>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
</body>
</html>
)
}5. Bedavaya RSS
Yukarıdaki rss yapılandırmasıyla sitelo build, /blog altındaki her sayfa için bir öğe içeren dist/rss.xml üretir — fazladan kod yok.
Yönlendirme belgeleri · Veri yükleme belgeleri · Yapılandırma belgeleri