A modern, customizable blog template built with Astro. Fast, responsive, and multilingual out of the box.
Inspiration: I loved the original morethan-log Next.js template so much that I ported it to Astro for my own site, sereja.com - and open-sourced the result as morethan-log-astro so fellow Astro fans can spin up a blazing-fast blog with the same clean design in seconds.
- 🌍 Multilingual Support - Built-in support for multiple languages (EN/RU by default)
- 📱 Responsive Design - Looks great on all devices
- 🌙 Dark Mode - Automatic theme switching based on system preferences
- 🔍 Search Functionality - Built-in search for your content
- 📝 Markdown Support - Write posts in Markdown with full syntax highlighting
- 🏷️ Categories - Organize posts by categories
- 📊 SEO Optimized - Meta tags, sitemap, multilingual RSS feeds included
- ⚙️ Highly Configurable - Easy customization through single config file
- 💬 Social Links - Add your social media profiles easily
- Node.js 18+ and npm
-
Quick Start with npm create (Recommended)
The easiest way to get started is using the Astro CLI:
npm create astro@latest -- --template JustSereja/morethan-log-astro
This command will:
- Prompt you for a project name
- Create a new directory with your blog
- Install all dependencies automatically
Then navigate to your project:
cd [your-project-name] -
Alternative: Use GitHub Template
If you prefer to create a GitHub repository first:
Then clone and set up:
git clone https://github.com/[your-username]/[your-repo-name].git cd [your-repo-name] npm install
Start the development server:
npm run devOpen your browser and visit http://localhost:4321 to see your blog!
npm run devnow handles all client-side bundles automatically—no extra watch commands needed. Edit files undersrc/and the Astro dev server takes care of the rest.
Your core site settings live in src/config/site.ts, while locale metadata (codes, labels, flags, language tags, defaults) is defined in src/config/locales.ts. Both files are fully typed so your editor can guide you.
const LOCALE_DEFINITIONS = [
{
code: 'en',
label: 'English',
nativeLabel: 'English',
langTag: 'en-US',
ogLocale: 'en_US',
flag: '🇬🇧',
dir: 'ltr',
isDefault: true,
},
{
code: 'ru',
label: 'Russian',
nativeLabel: 'Русский',
langTag: 'ru-RU',
ogLocale: 'ru_RU',
flag: '🇷🇺',
dir: 'ltr',
},
] as const;codedrives URL prefixes, content folder names, and the language switcher.langTagbecomes the<html lang>attribute and informs search engines.ogLocalecustomizes Open Graph metadata for each language.flag,label, andnativeLabelpower the UI (change or remove them as you like).- Mark exactly one locale with
isDefault: true; everything else will derive from it.
When you add a new locale:
- Extend
LOCALE_DEFINITIONS. - Provide matching values in
src/config/site.ts(titles, descriptions, author info, category labels, etc.). - Add or update translations in
src/i18n/ui.tsto localize UI strings not pulled from config.
import type { SiteConfig } from '@config';
const siteConfig: SiteConfig = {
siteUrl: 'https://morethan-log-astro.sereja.com',
title: {
en: 'Morethan-Log',
ru: 'Morethan-Log',
},
description: {
en: 'A modern blog template built with Astro',
ru: 'Современный шаблон блога на Astro',
},
author: {
name: {
en: 'Sereja',
ru: 'Серёжа',
},
email: 'demo@morethan-log.com',
avatar: '/img/avatar.svg',
bio: {
en: 'Full-stack developer passionate about the web.',
ru: 'Full-stack разработчик, увлеченный вебом.',
},
},
// ...see the file for the complete option list
};
export default siteConfig;Add any contact or social profiles:
contactLinks: [
{
id: 'github',
label: {
en: 'GitHub',
ru: 'GitHub',
},
url: {
en: 'https://github.com/yourusername',
ru: 'https://github.com/yourusername-ru',
},
icon: '🐙',
},
{
id: 'newsletter',
label: {
en: 'Newsletter',
ru: 'Рассылка',
},
url: 'https://example.com/newsletter',
},
];Configure blog categories:
categories: {
blog: {
enabled: true,
path: "/blog",
icon: "💻",
label: {
en: 'Blog',
ru: 'Блог',
},
description: {
en: 'Personal thoughts, experiences, and insights',
ru: 'Личные мысли, опыт и идеи',
},
},
// Add more categories
}pathcontrols the base segment used in URLs (e.g./insights). Keep it to a single segment (with or without the leading slash) and the template will add language prefixes automatically.
Define the header menu in src/config/site.ts:
navigation: [
{
id: 'about',
labelKey: 'ui.about', // Uses the translation helper
translationKey: 'about', // Targets the localized page whose filename (minus locale) is "about"
},
{
id: 'projects',
labelKey: 'ui.projects',
path: '/projects', // Direct path that will be localized automatically
},
{
id: 'github',
label: { en: 'GitHub', ru: 'GitHub' },
external: 'https://github.com/yourusername',
},
];- Use
translationKeyto link to pages defined insrc/content/pages/**; no frontmatter is needed—identifiers are derived from each file's path automatically, even when per-localepermalinkvalues differ. - Use
pathfor simple internal links (without a language prefix); the layout will localize them. - Use
externalfor outbound URLs. labelKeypulls fromsrc/i18n/ui.ts; you can also provide a per-localelabeloverride.
Toggle features on/off:
features: {
darkMode: true,
search: true,
rss: true,
// ... more features
}Control how dates render per language:
dateFormats: {
en: {
locale: 'en-US',
options: { year: 'numeric', month: 'long', day: 'numeric' },
},
ru: {
locale: 'ru-RU',
options: { year: 'numeric', month: 'long', day: 'numeric' },
},
}-
Create a new
.mdfile in the appropriate directory:- Blog posts:
src/content/posts/en/blog/ - Technology posts:
src/content/posts/en/technology/ - Projects:
src/content/posts/en/projects/
- Blog posts:
-
Add frontmatter using the typed schema:
---
title: 'Your Post Title'
h1: 'Display Title'
description: 'A brief description of your post'
date: '2024-03-15'
announcement: 'Optional summary shown in lists'
image: '/img/posts/your-image.jpg'
permalink: 'my-custom-slug' # Optional: override the URL segment
aiGenerated: false # Optional: flag AI-assisted content
draft: false # Optional: keep the post out of production builds
---
Your post content here...The folder path
src/content/posts/<lang>/<category>/defines the language and category automatically—no extra frontmatter needed.
- Posts: Set
permalinkto override just the slug segment (e.g.permalink: 'case-study-2024'). Keep it to a single segment; the template combines it with the configured category path and language prefix. - Pages: Add
permalinkto entries insrc/content/pages/**when you want a top-level custom route (e.g.permalink: 'about-me'). Nested paths (company/about) are supported, and each language can provide its own value. - Categories: Adjust
pathinsrc/config/site.tsto change the category segment (for examplepath: '/case-studies'). All posts in the category and the listing page will pick up the new URL.
The build still links translations by file structure, so permalink changes never break cross-language hreflang relationships.
| Field | Type | Description | Required | Default |
|---|---|---|---|---|
title |
string |
Primary title shown in listings and <title> tags |
✅ | — |
h1 |
string |
Overrides the in-article heading | ❌ | falls back to title |
description |
string |
SEO/meta description for cards and listings | ❌ | — |
date |
string (ISO) |
Publication date, used for sorting | ✅ | — |
announcement |
string |
Short summary displayed on cards | ❌ | — |
image |
string |
Featured image path or URL | ❌ | Category/ global fallback |
aiGenerated |
boolean |
Marks content as AI-assisted to surface a banner | ❌ | false |
permalink |
string |
Custom slug for the post URL (single segment) | ❌ | Derived from filename |
draft |
boolean |
Exclude the entire translation set from production builds, feeds, search, and sitemaps | ❌ | false |
Create a matching file under src/content/posts/ru/<category>/ with the same file name. The build system derives language, category, and cross-language links from the folder structure automatically.
src/content/posts/en/blog/my-post.md # English
src/content/posts/ru/blog/my-post.md # Russian
- Posts and pages can be authored as
.mdxfiles—the same frontmatter schema applies, so drafts, permalinks, and AI flags continue to work exactly like.md. - React islands live in
src/components/islands/**. Each island exports its component as the default export and can optionally provide metadata via anislandnamed export. - The registry automatically discovers every island folder. Import them directly from their folder (e.g.
import DemoCounter from '@components/islands/DemoCounter';) or reuse them across multiple posts without touching a central index. - When hydrating inside MDX, attach the usual directives (
<DemoCounter client:load initial={3} />) and Astro will stream the static HTML before React takes over.
The template supports both multilingual and single-language content:
- Multilingual posts: keep the same file name across languages so the auto-generated cross-link stays in sync.
- Single-language posts: create a single entry; the language switcher gracefully falls back to the homepage of other locales.
This is perfect for:
- Language-specific announcements
- Regional content
- Technical documentation in one language
- Gradual content translation
The template provides multilingual RSS feeds with full content support:
- Main Feed (
/rss.xml) - Contains posts in the default language (English) - English Feed (
/en/rss.xml) - English posts only - Russian Feed (
/ru/rss.xml) - Russian posts only
This approach ensures subscribers never receive content in languages they don't understand. The main feed (/rss.xml) serves the default language to maintain compatibility with RSS readers that expect a feed at this standard location.
Each RSS feed includes:
- ✅ Full HTML content (not just descriptions)
- ✅ Properly converted image URLs (relative to absolute)
- ✅ Author information
- ✅ Post categories
- ✅ All required RSS 2.0 elements
RSS feeds are automatically linked in the <head> of each page:
- The main feed (
/rss.xml) contains default language content - Language-specific feeds are available at
/{lang}/rss.xml - Alternative language feeds include
hreflangattributes
To change which language appears in the main RSS feed, update defaultLanguage in src/config/site.ts:
// src/config/site.ts
export default {
// ...
defaultLanguage: "ru", // Change to make Russian the main feed language
// ...
}- Main styles:
public/css/style.css - Modify CSS variables for colors and themes
- Dark mode styles are included
The template includes category-specific placeholder images for posts without featured images:
- Blog posts:
/public/img/posts/placeholder-blog.svg(Purple gradient with document icon) - Technology posts:
/public/img/posts/placeholder-technology.svg(Green gradient with code terminal) - Projects posts:
/public/img/posts/placeholder-projects.svg(Orange gradient with gear icon) - Default:
/public/img/posts/placeholder.svg(Simple fallback)
Posts automatically use the appropriate placeholder based on their category.
For RSS feeds, the template supports both SVG and PNG formats:
- Create your logo as
/public/img/rss-logo.png(144x144px) for best compatibility - Falls back to
/public/img/rss-logo.svgif PNG doesn't exist - PNG format is recommended as it's more universally supported by RSS readers
- Add language to
src/i18n/ui.ts:
export const languages = {
en: 'English',
ru: 'Русский',
es: 'Español' // New language
};- Add translations:
export const ui = {
// ... existing languages
es: {
'name': SITE_CONFIG.author.name,
'ui.about': 'Acerca de',
// ... more translations
}
}Create new pages in src/pages/ using .astro or .md files.
The template now supports fully configurable contact/social links and author names for each language:
-
Contact & Social Links: Configure any set of links in
src/config/site.ts. Each entry can localize its label and URL, and optionally define an icon (emoji) or raw SVG:contactLinks: [ { id: "github", label: { en: "GitHub", ru: "GitHub" }, url: { en: "https://github.com/EnglishUsername", ru: "https://github.com/RussianUsername" }, icon: "🐙" }, { id: "portfolio", label: { en: "Portfolio", ru: "Портфолио" }, url: "https://example.com", iconSvg: "<svg ...>...</svg>" } // ...other links ]
-
Language-Specific Author Names: Set different author names for each language:
author: { name: { en: "John Doe", ru: "Иван Иванов" }, // ... other author fields }
npm run buildThe build pipeline includes a post-build step that formats the output and copies dist/404.html to dist/404/index.html. This guarantees that providers expecting a directory-style 404 route (e.g. GitHub Pages, Netlify) correctly serve your not-found page whether the request is for /404 or /404.html.
Keeping your project in sync with the upstream template is easiest when you track the original repository and selectively pull in changes.
From your project root, run:
make update-templateThis command:
- Clones the latest
mainbranch of the upstream template into.template-update - Syncs framework, layout, scripts, styles, and assets into your project using
rsync - Leaves your content (
src/content/**), personal config (src/config/site.ts,src/config/locales.ts), and images (public/img/**, favicons) untouched - Cleans up the temporary clone when finished
- Anything under
public/css/is considered template-owned; keep personal overrides elsewhere or reapply them after the sync
Requirements:
gitandrsyncinstalled (macOS/Linux ship with both; on Windows, use WSL or install via package manager)- Any local changes committed or stashed so you can review the diff the command produces
After it runs, review git status, resolve conflicts (if any), reinstall deps when package.json changes, and run npx astro sync followed by npm run build to double-check everything still compiles.
Run the following once inside your project folder:
git remote add upstream https://github.com/JustSereja/morethan-log-astro.git
git fetch upstream --tagsFrom now on, git fetch upstream --tags pulls the latest commits and release tags. You can inspect what changed with git log upstream/main.
If you want the full template (including demo posts and placeholder config) in your project:
git checkout main
git pull
git merge upstream/mainResolve any conflicts, test the build, then commit the merge.
To grab only the template code while keeping your own content and configuration, restore just the framework directories from the template branch (or a specific release tag):
# Update TAG if you prefer a specific release, e.g. upstream/v2.0.0
TARGET_REF=upstream/main
git fetch upstream --tags
git checkout main
git pull
git restore --source "$TARGET_REF" \
astro.config.mjs \
package.json package-lock.json \
tsconfig.json \
public/css public/favicon.ico public/favicon.svg public/img \
scripts \
src/components src/i18n src/layouts src/lib src/pages src/scripts src/utils
# keep your live content and custom site settings
git checkout -- src/content src/config/site.ts src/config/locales.ts
npm install
npx astro sync
npm run buildThis sequence:
- Brings in the latest template logic, layouts, scripts, and assets.
- Leaves
src/content/**untouched, so your posts and pages stay intact. - Restores your own
src/config/site.tsandsrc/config/locales.ts, keeping personal branding, locale metadata, contact links, and other secrets.
Review git status, commit the updated files, and (optionally) create a tag for the new version of your site.
