Complete Guide to Astro Shibui Template: Build Your Blog from Scratch

Table of Contents

    Welcome to Astro Shibui Template! This is an elegant, minimalist Astro blog template with multilingual support, podcast features, and search functionality. This guide will walk you through using this template to build your blog from scratch.

    Quick Start

    Prerequisites

    Make sure you have installed:

    • Node.js >= 18.x
    • pnpm >= 8.x (npm/yarn also supported)

    Install Project

    # Clone or download the project
    cd astro-shibui-template
    
    # Install dependencies
    pnpm install
    

    Configure Environment Variables

    cp .env.example .env
    

    Edit .env file:

    # Site information
    PUBLIC_SITE_URL=http://localhost:4321
    PUBLIC_SITE_NAME="My Blog"
    PUBLIC_SITE_DESCRIPTION="My personal blog"
    
    # Algolia search (optional, configure later)
    # ALGOLIA_APP_ID=your_app_id
    # ALGOLIA_SEARCH_KEY=your_search_key
    # ALGOLIA_INDEX_NAME=your_index_name
    
    # Podcast feature (optional, configure later)
    # PUBLIC_PODCAST_ENABLED=false
    # PUBLIC_PODCAST_AUDIO_BASE_URL=https://your-cdn.com/
    

    Start Development Server

    pnpm dev
    

    Visit http://localhost:4321/ to see your blog!

    Project Structure

    Understanding the project structure helps you customize the template better:

    astro-shibui-template/
    ├── src/
    │   ├── lib/
    │   │   └── config.ts          # Core configuration ⭐
    │   ├── content/
    │   │   ├── blog-zh/           # Chinese posts
    │   │   └── blog-en/           # English posts
    │   ├── layouts/
    │   │   ├── BaseLayout.astro    # Base layout
    │   │   └── CoverLayout.astro   # Cover layout
    │   ├── components/
    │   │   ├── Search.astro       # Search component
    │   │   ├── PodcastPlayer.tsx  # Podcast player
    │   │   └── ...
    │   ├── pages/
    │   │   ├── index.astro        # Homepage
    │   │   ├── blog/              # Blog pages
    │   │   └── ...
    │   └── styles/
    │       ├── global.css         # Global styles
    │       └── podcast-player.css # Podcast styles
    ├── public/
    │   └── images/               # Static image assets
    ├── .env.example              # Environment variable template
    └── astro.config.mjs          # Astro configuration
    

    Core Configuration

    Site Information

    Edit src/lib/config.ts:

    export const siteConfig = {
      // Basic info
      name: "My Blog",
      description: "My learning and thoughts",
      author: "Your Name",
      url: "http://localhost:4321",  // Change to your domain in production
    
      // Multilingual
      locales: {
        default: "zh-cn",
        available: ["zh-cn", "en"],
      },
    
      // Feature toggles
      features: {
        podcast: {
          enabled: false,  // Enable podcast
          audioBaseUrl: "",
        },
        search: {
          enabled: true,   // Enable search
          provider: "algolia",
        },
      },
    };
    

    Multilingual Setup

    The template supports Chinese and English:

    • Chinese posts: Place in src/content/blog-zh/
    • English posts: Place in src/content/blog-en/

    Language switching works automatically based on URL routing:

    • Chinese: http://localhost:4321/blog/
    • English: http://localhost:4321/en/blog/

    Content Management

    Create Posts

    Create a new Markdown file in src/content/blog-zh/:

    ---
    title: "Post Title"
    description: "Post description"
    date: 2026-03-13
    tags: ["tag1", "tag2"]
    toc: true  # Show table of contents
    images: ["/images/cover.jpg"]  # Cover image (optional)
    draft: false  # Draft status
    categories: ["Technology"]
    ---
    
    # Post Title
    
    Post content goes here...
    

    Frontmatter Fields

    FieldTypeRequiredDescription
    titlestringPost title
    descriptionstringPost description
    dateDatePublish date
    tagsarrayTags list

    Tip: When toc: true is enabled, the template automatically numbers your headings (e.g., 1.1, 1.2, 2.1) and generates a clickable table of contents.

    Image Management

    Astro Shibui template provides comprehensive image handling features including cover images, inline images, and social sharing images.

    Image Directory Structure

    Place images in the public/images/ directory:

    public/images/
    ├── cover.jpg           # Post cover image
    ├── photo.jpg           # Inline image
    ├── icons/             # Site icons
    │   ├── favicon.ico
    │   ├── apple-touch-icon.png
    │   └── ...
    ├── generated/         # Auto-generated social images
    │   └── social/        # Social sharing images (auto-generated)
    └── terms/            # Tag cover images
    

    Cover Images

    Set cover image in post frontmatter:

    ---
    title: "Post Title"
    images: ["/images/cover.jpg"]  # Cover images list
    ---
    

    Cover images appear in:

    • 📱 Homepage post cards
    • 📄 Article detail page top (if set)
    • 🌐 Social media sharing cards

    Tag Cover Images

    Tag pages support custom cover images. Place images in public/images/terms/ directory:

    public/images/terms/
    ├── tech.jpg
    ├── philosophy.png
    └── life.webp
    

    Rules:

    • Filename is tag name + extension (e.g., tech.jpg)
    • Supported formats: .jpg, .jpeg, .png, .webp
    • System will automatically find and display the cover image for the corresponding tag

    Inline Images

    Reference directly in Markdown:

    ![Image description](/images/photo.jpg)
    

    Automatic Features:

    • Auto-generate Figure tags: <img> tags are automatically wrapped in <figure>
    • Auto-generate Figcaption: Alt text is used as image caption
    • Smart processing: Images that exist alone in paragraphs are automatically expanded to complete figure structures

    For example:

    ![Beautiful landscape](/images/landscape.jpg "Hills at sunset")
    

    Automatically converts to:

    <figure>
      <img src="/images/landscape.jpg" alt="Beautiful landscape" title="Hills at sunset">
      <figcaption>Beautiful landscape</figcaption>
    </figure>
    

    Social Sharing Images

    The template automatically generates social media sharing images (1200x630):

    Automatic Processing Flow:

    1. 📸 Read cover image from frontmatter images field
    2. ✂️ Automatically crop and optimize using Sharp
    3. 💾 Save to public/generated/social/ directory
    4. 🌐 Use in OG/Twitter Card

    Generation Rules:

    • Format: JPEG (quality: 82)
    • Size: 1200x630
    • Crop method: cover + attention
    • Filename: SHA1 hash based on image path

    Supported Types:

    • Local images: .jpg, .jpeg, .png, .webp, .avif, .svg
    • External URLs: Unsplash and other images automatically add crop parameters
    • Auto-cache: Unmodified images won’t be regenerated

    Image Path Processing

    The template provides intelligent image path processing:

    Auto-normalization:

    // Automatically add leading "/"
    images/photo.jpg → /images/photo.jpg
    // Keep absolute URLs unchanged
    https://example.com/image.jpg → https://example.com/image.jpg
    // Keep data URLs unchanged
    data:image/png;base64,... → data:image/png;base64,...
    

    Extract Images from Markdown: If no cover image is set, the system automatically extracts the first image from article content:

    Some text...
    ![First image](/images/first.jpg)
    More text...
    

    The system will automatically identify and use /images/first.jpg as the cover image.

    External Images as Cover

    Use external URLs directly in frontmatter, no need to download images.

    ---
    title: "My Post"
    images: ["https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&fit=crop&w=1200&h=630&q=80"]
    ---
    

    Supported: Unsplash, Pexels, Pixabay, Burst - any image URL.

    Getting Unsplash direct image URL:

    1. Find a photo you like on Unsplash
    2. Right-click the photo → “Open image in new tab”
    3. If it opens the photo detail page, repeat: right-click → “Open image in new tab”
    4. Repeat until the browser shows only the pure image (no website elements, like a local image)
    5. Copy the URL from the browser address bar, format like:
      https://images.unsplash.com/photo-xxxxx?auto=format&fit=crop&w=1200&q=80
      

    Cover displays at:

    • 📱 Homepage post cards
    • 📄 Article detail page top
    • 🌐 Social media cards (auto-cropped to 1200x630)

    Usage examples:

    ---
    title: "Using Unsplash"
    images: ["https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&fit=crop&w=1200&h=630&q=80"]
    ---
    
    # Article content...
    
    ---
    title: "Using Pexels"
    images: ["https://images.pexels.com/photos/268533/pexels-photo-268533.jpeg?w=1200&h=630"]
    ---
    
    # Article content...
    
    ---
    title: "Using any external URL"
    images: ["https://example.com/image.jpg"]
    ---
    
    # Article content...
    

    Notes:

    • External images depend on network, may not load if service is down
    • Most free image libraries allow commercial use, check license
    • Recommended to add image source attribution at article end

    Image Best Practices

    URL format:

    https://images.pexels.com/photos/{photo_id}/{image_name}.jpg
    

    Example:

    ---
    images: [
      "https://images.pexels.com/photos/268533/pexels-photo-268533.jpeg?auto=compress&cs=tinysrgb&w=1200&h=630"
    ]
    ---
    

    Features:

    • ✅ Completely free, commercial use allowed
    • ✅ High quality photos
    • ✅ CDN supported

    Pixabay

    URL format:

    https://pixabay.com/get/{hash}_{image_name}
    

    Example:

    ---
    images: [
      "https://cdn.pixabay.com/photo/2016/11/29/05/45/astronomy-1867616_1280.jpg"
    ]
    ---
    

    Features:

    • ✅ Completely free, commercial use allowed
    • ✅ SVG and vector graphics supported
    • ✅ Provides thumbnails

    Burst (Shopify)

    URL format:

    https://burst.shopifycdn.com/photos/{image_name}
    

    Example:

    ---
    images: [
      "https://burst.shopifycdn.com/photos/workspace-setup/343b7e26f6d3a3e04d446e7774613a7.jpg"
    ]
    ---
    

    Features:

    • ✅ Official from Shopify, high quality
    • ✅ Business friendly
    • ✅ No registration needed

    Benefits of External Images

    Advantages of using external images:

    1. Save storage space: No need to upload images to server
    2. Global CDN: Leverage image library’s CDN
    3. Auto-optimization: Multiple sizes and formats provided
    4. High quality: Professional photographer works
    5. Free licensing: Most are commercially usable
    6. Quick start: Copy URL and use immediately

    Image Best Practices

    Example:

    Photo source: Unsplash
    

    Image Best Practices

    ---
    
    **Photo source**: Unsplash
    **Photographer**: [Photographer Name](https://unsplash.com/@{username})
    **License**: Unsplash License
    

    mage Best Practices

    Image Optimization:

    1. Compress images: Use TinyPNG or Squoosh
    2. Use modern formats: WebP > JPEG > PNG
    3. Provide appropriate alt text: Better for SEO and accessibility
    4. Use responsive images: Load different sizes for different devices

    Example:

    ---
    title: "My Travel Journal"
    description: "Documenting my journey through Europe"
    date: 2026-03-13
    images: [
      "/images/travel/cover.webp",  # Main cover
      "/images/travel/photo1.jpg",  # Other images
    ]
    toc: true
    ---
    
    # My European Journey
    
    ![Eiffel Tower at sunset](/images/travel/eiffel.webp "Eiffel Tower in Paris")
    
    This was the most memorable moment of my trip...
    

    Auto-generate Social Images

    Auto-executed during build (pnpm build):

    # Prebuild script will:
    pnpm run prebuild  # → scripts/generate-social-images.ts
    

    Generation Result Example:

    [social-image] local images: 5, generated: 2, skipped: 3
    
    • local images: Total local images found
    • generated: Number of newly generated social images
    • skipped: Existing and unmodified images

    External Image Support

    Supports external image services like Unsplash:

    ---
    images: [
      "https://images.unsplash.com/photo-1234567890"
    ]
    ---
    

    System automatically adds crop parameters:

    ?auto=format&fit=crop&w=1200&h=630&q=80&fm=jpg
    

    Image Version Control

    Social sharing images support version control to avoid cache issues:

    Environment Variables:

    PUBLIC_SOCIAL_IMAGE_VERSION=auto  # Auto-generate
    # or
    PUBLIC_SOCIAL_IMAGE_VERSION=v1.0  # Manually specify version
    

    Auto Version:

    • Vercel: VERCEL_GIT_COMMIT_SHA
    • Cloudflare Pages: CF_PAGES_COMMIT_SHA
    • Build time: BUILD_TIME

    Common Issues

    Q: Images not showing? A: Check the following:

    1. Image path is correct (must start with /)
    2. Image file exists in public/ directory
    3. Image filename is case-sensitive

    Q: Social images not generating? A: Ensure:

    1. Post frontmatter has images field
    2. Image path points to local image (external images won’t generate)
    3. Image format is supported (see section 4.3.5)

    Q: How to disable auto figure generation? A: Modify configuration in src/lib/rehype-image-figure.ts or remove the plugin.

    Q: How to customize image styles? A: Add to src/styles/global.css:

    img {
      max-width: 100%;
      height: auto;
      border-radius: 8px;
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    }
    
    figure {
      margin: 2rem 0;
    }
    
    figcaption {
      text-align: center;
      color: var(--color-text-muted);
      font-size: 0.9rem;
      margin-top: 0.5rem;
    }
    

    Advanced Image Processing

    Using Astro Image Component (optional):

    ---
    import { Image } from 'astro:assets';
    ---
    
    <Image 
      src="/images/photo.jpg" 
      alt="Description"
      width={1200}
      height={630}
      format="webp"
      quality={80}
    />
    

    Creating Image Sets:

    ---
    import { Image } from 'astro:assets';
    const image = {
      src: "/images/hero.jpg",
      alt: "Hero Image",
    };
    const sizes = [400, 800, 1200];
    ---
    
    <picture>
      {sizes.map(size => (
        <source 
          media={`(max-width: ${size}px)`} 
          srcset={image.src.replace('.jpg', `-${size}.webp`)} 
        />
      ))}
      <Image {...image} />
    </picture>
    

    Feature Configuration

    Enable Search Function

    Step 1: Register Algolia

    1. Visit Algolia to sign up
    2. Create new Application
    3. Create Index
    4. Get API Keys

    Step 2: Configure Environment

    Add to .env:

    ALGOLIA_APP_ID=your_app_id
    ALGOLIA_SEARCH_KEY=your_search_key
    ALGOLIA_INDEX_NAME=blog_posts
    ALGOLIA_ADMIN_KEY=your_admin_key  # Only for uploading index
    

    Step 3: Generate and Upload Index

    # Generate search index
    pnpm build-index
    
    # Upload to Algolia
    pnpm upload-index
    
    # Or do both at once
    pnpm algolia
    

    Search box will appear automatically on the blog archive page (/blog/).

    Keyboard shortcut: Cmd/Ctrl + K to focus search box quickly.

    Enable Podcast Feature

    Podcast is disabled by default. To enable:

    Step 1: Configure Audio Storage (Cloudflare R2)

    Recommended: Use Cloudflare R2 for audio storage (no bandwidth fees).

    Create R2 Bucket:

    1. Login to Cloudflare Dashboard
    2. Go to R2 → Create bucket, name it like podcasts
    3. Add custom domain (e.g., r2.yourdomain.com), domain must be on Cloudflare
    4. Upload audio files

    Audio File Naming:

    • Chinese posts: post slug + .m4a (e.g., my-article.m4a)
    • English posts: post slug + .en.m4a (e.g., my-article.en.m4a)

    Audio URL Format:

    https://r2.yourdomain.com/my-article.m4a
    https://r2.yourdomain.com/my-article.en.m4a
    

    Step 2: Enable Feature Toggle

    In src/lib/config.ts:

    features: {
      podcast: {
        enabled: true,
      },
    }
    

    Add to .env:

    # Podcast audio storage URL
    PUBLIC_PODCAST_AUDIO_BASE_URL=https://r2.yourdomain.com/
    

    Note: PUBLIC_PODCAST_AUDIO_BASE_URL should end with a slash.

    Step 3: Add Podcast to Posts

    Add to post frontmatter:

    ---
    title: "Post Title"
    podcast: true  # Enable podcast
    ---
    

    Step 4: Upload Audio Files

    Audio file naming: {post-slug}.m4a

    Example:

    • Post: hello-world.md
    • Audio: hello-world.m4a (Chinese)
    • Audio: hello-world.en.m4a (English)

    Upload audio files to your configured CDN.

    The podcast player will appear at the bottom of the page with:

    • ▶️ Play/pause
    • ⏪ Progress bar
    • 🔊 Playback speed (0.5x - 2x)
    • 📋 Playlist
    • 🔄 Cross-page continuous playback

    Enable Comments (Optional)

    The template supports Giscus comments:

    1. Enable Discussions on your GitHub repository
    2. Visit Giscus to configure and get your settings
    3. Add to src/lib/config.ts:
    features: {
      comments: {
        enabled: true,
        giscus: {
          repo: "username/repo",           // Your GitHub repo
          repoId: "R_kgDOGxxxx",          // Repo ID (starts with R_)
          category: "Announcements",       // Discussion category
          categoryId: "DIC_kwDOGxxxx",    // Category ID (starts with DIC_)
        },
      },
    }
    

    Comment Display Location

    Comments will automatically appear at:

    • 📄 Article detail page (bottom)
    • Only on Chinese and English article pages
    • Not on homepage, archive pages, etc.

    Custom Comment Styles

    Edit src/components/Giscus.astro if needed.

    Style Customization

    Theme Colors

    Edit src/styles/global.css, modify CSS variables:

    :root {
      /* Colors */
      --color-bg-primary: light-dark(#faf9f5, #262624);
      --color-bg-secondary: light-dark(hsl(48 25% 92.2% / 1), hsl(60 3% 8% / 1));
      --color-border: light-dark(#e5decf, #4a4037);
      --color-text-primary: light-dark(#000, #faf5f9);
      --color-text-muted: light-dark(#888, #a69885);
    }
    

    Font Configuration

    Web Fonts

    Edit astro.config.mjs:

    fonts: [
      {
        name: "Noto Serif SC",
        cssVariable: "--font-noto-serif-sc",
        provider: fontProviders.google(),
        weights: [400, 700],
        subsets: ["latin"],
        fallbacks: ["Georgia", "serif"],
      },
    ],
    

    Local Fonts

    In src/styles/global.css:

    :root {
      --font-family-primary: "Your Font", serif;
    }
    

    Layout Adjustments

    :root {
      --container-width: 56ch;  /* Content width */
      --sidebar-width: 300px;     /* Sidebar width */
    }
    

    Responsive Breakpoints

    The template’s responsive layout:

    • ≤ 689px: Single-column layout (mobile), left menu becomes full-screen navigation
    • 690px - 1500px: Two-column layout, both homepage and article pages have left cover + navigation, right content
    • > 1500px: Three-column layout, article pages display table of contents/sidebar widgets on the right
    /* Large screens */
    @media screen and (min-width: 1600px) {
      :root {
        --container-width: 60ch;
      }
    }
    
    /* Mobile */
    @media screen and (max-width: 689px) {
      .content-wrapper {
        max-width: 100%;
      }
    }
    

    Advanced Customization

    Custom Components

    Create in src/components/:

    ---
    interface Props {
      title: string;
    }
    
    const { title } = Astro.props;
    ---
    
    <div class="my-component">
      <h2>{title}</h2>
    </div>
    
    <style>
      .my-component {
        padding: 1rem;
        background: var(--color-bg-secondary);
      }
    </style>
    

    Add New Page Types

    For example, create a tag page:

    ---
    // src/pages/tags/[tag].astro
    import { getCollection } from "astro:content";
    
    export async function getStaticPaths() {
      const posts = await getCollection("blog-en");
      const tags = [...new Set(posts.flatMap(post => post.data.tags))];
    
      return tags.map(tag => ({
        params: { tag },
      }));
    }
    
    const { tag } = Astro.params;
    ---
    
    <CoverLayout title={`Tag: ${tag}`}>
      <h1>Tag: {tag}</h1>
      <!-- Tag post list -->
    </CoverLayout>
    

    Integrate Third-party Services

    Add Umami Analytics

    Configure in .env:

    PUBLIC_UMAMI_WEBSITE_ID=your-website-id
    

    Add scripts in pages as needed (refer to documentation).

    Add Google Analytics

    Create public/gtag.js, import in layout.

    Performance Optimization

    Image Optimization

    Use Astro’s Image component:

    import { Image } from "astro:assets";
    
    <Image src={imageSrc} alt="Description" />
    

    Code Splitting

    Use client:idle or client:visible directives:

    <HeavyComponent client:idle />
    

    Preload Critical Resources

    In layout <head>:

    <link rel="preload" href="/fonts/main.woff2" as="font">