How I Made My Blog Faster and More Feature-Rich

Recently, I’ve been feeling that my blog loads slowly.

Especially with image-heavy articles, it takes time to display, which can be stressful when reading.

So I decided to take the plunge and work on improving my blog.

What I Implemented

Performance Improvements

The first thing I tackled was improving loading speed.

Image Optimization

Problems

  • Images were being served as PNG or JPEG, with large file sizes
  • Always loading the same size images regardless of screen size
  • Lazy loading wasn’t implemented

Solutions

I created a component called OptimizedImage.astro.

<Picture
  src={imageSrc}
  alt={alt}
  formats={['webp', 'jpeg']}
  widths={[400, 800, 1200]}
  sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
  loading={loading}
/>

Using Astro’s Picture component, I automatically convert to WebP format and generate multiple sizes.

Results

  • Average 60-70% reduction in image file sizes
  • Now serving smaller images on mobile and larger on desktop, reducing unnecessary data transfer
  • Noticeably faster image display

Critical CSS Implementation

Problems

  • Loading all CSS at once made initial display slow
  • Unused styles were loaded from the start

Solutions

I implemented Critical CSS in MainLayout.astro.

I embedded only the minimal CSS needed for the first view inline, and loaded the rest asynchronously.

<style is:inline>
  /* Critical CSS */
  body { margin: 0; font-family: sans-serif; }
  .container { max-width: 1200px; margin: 0 auto; }
  /* ... */
</style>

Results

  • First Contentful Paint (FCP) improved by about 30%
  • Visibly faster initial page display

Service Worker Cache Strategy

Problems

  • Fetching the same resources from the server repeatedly
  • Couldn’t display anything offline

Solutions

I created sw.js and implemented a cache-first strategy.

// Cache-first strategy
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      return response || fetch(event.request).then((fetchResponse) => {
        return caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request, fetchResponse.clone());
          return fetchResponse;
        });
      });
    })
  );
});

Results

  • Blazing fast access from the second visit (served instantly from cache)
  • Previously viewed pages can be displayed even offline

Problems

  • Black spaces next to images looked bad
  • Couldn’t tell what articles were about at all

Solutions

I significantly revised FeaturedPostsCarousel.astro.

First, I created a function to get excerpts from article content:

function getExcerpt(body: string, maxLength: number = 150): string {
  if (!body) return '';
  const plainText = body
    .replace(/^#+\s+/gm, '') // Remove headings
    .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert links to text
    .replace(/[*_~`]/g, '') // Remove markdown formatting
    .trim();
  
  return plainText.length > maxLength 
    ? plainText.substring(0, maxLength) + '...' 
    : plainText;
}

Then changed the layout to overlay text on images:

.carousel-text-overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: linear-gradient(to top, rgba(0,0,0,0.9), transparent);
  color: white !important;
}

Results

  • Article content is now visible at a glance
  • Modern and stylish design
  • Text is readable in both light and dark modes

New Feature Additions

Besides performance improvements, I also added features to improve usability.

PWA Support

Problems

  • Only accessible through browser
  • Just a shortcut when added to home screen

Solutions

Created manifest.json:

{
  "name": "semiramisu blog",
  "short_name": "semiramisu",
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#0ea5e9",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ]
}

Combined with Service Worker to function as a complete PWA.

Results

  • When added to smartphone home screen, looks like a native app
  • Address bar hidden for an immersive experience
  • Basic functionality works even offline

Comment Feature (Giscus)

Problems

  • No way to receive feedback on articles
  • Can’t communicate with readers

Solutions

Created Comments.astro component and integrated Giscus:

<script src="https://giscus.app/client.js"
  data-repo="semiramisu/semiramisu.github.io"
  data-repo-id="YOUR_REPO_ID"
  data-category="Comments"
  data-category-id="YOUR_CATEGORY_ID"
  data-mapping="pathname"
  data-theme={theme}
  async>
</script>

Also implemented dark mode support, so the comment section theme automatically changes when the theme switches.

Results

  • Users can log in with GitHub account and comment
  • GitHub handles spam prevention, so it’s secure
  • Conversations can continue in discussion format

Enhanced Search Functionality

Problems

  • Search function wasn’t working at all
  • Pagefind script wasn’t loading correctly

Solutions

Fixed SearchBar.astro and properly initialized Pagefind:

// Dynamically load Pagefind script
const script = document.createElement('script');
script.src = '/pagefind/pagefind.js';
document.head.appendChild(script);

// Execute search
const search = await pagefind.search(query);

Also improved search result display to show just title and tags simply:

<a href="${data.url}" class="search-result-item">
  <div class="search-result-title">${title}</div>
  <div class="search-result-tags">${tagsHtml}</div>
</a>

Results

  • Fast search even in Japanese
  • Search results are clear and easy to click
  • Real-time results display with incremental search

Problems

  • Displayed large at the end of articles, requiring long scrolls after reading
  • When moved to sidebar, images and titles were too small to see

Solutions

Added compact mode to RelatedPosts.astro:

{isCompact ? (
  <div class="related-posts-list">
    {finalPosts.map((post) => (
      <a href={`/posts/${IdToSlug(post.id)}/`} class="related-post-item">
        <h3 class="related-post-title">{post.data.title}</h3>
        <div class="related-post-tags">
          {post.data.tags.slice(0, 3).map(tag => (
            <span class="related-post-tag">{tag}</span>
          ))}
        </div>
      </a>
    ))}
  </div>
) : (
  // Normal display
)}

Results

  • Readable display even in sidebar
  • Removed images to focus on text information
  • By placing under table of contents, users can check related articles while reading

Other Minor Improvements

RSS Feed Improvements

  • Problem: Only titles, no content included
  • Fix: Used MarkdownIt and sanitize-html to convert Markdown to HTML before distribution
  • Result: Article content now readable in feed readers

Table of Contents Improvements

  • Problem: Table of contents gets in the way for long articles
  • Fix: Added collapse feature and reading progress indicator
  • Result: Can expand only when needed, shows reading progress

Enhanced Mobile Support

  • Problem: Poor mobile usability
  • Fix: Implemented pull-to-refresh, swipe navigation, bottom navigation
  • Result: Comfortable operation on smartphones

Problems Encountered During Implementation and Solutions

Netlify Build Error

Build errors occurred because pnpm-lock.yaml was outdated.

The error message showed dependency inconsistencies were the cause.

Resolved by running pnpm install to update the lockfile.

Tailwind CSS Circular Reference

Circular reference error occurred when using @apply visible.

Tailwind’s @apply internally uses CSS custom properties, causing issues when combined with visibility property.

Resolved by directly using visibility: visible.

Navigation broke when adding bookmark feature.

The cause was adding too many elements to the navigation bar, breaking responsive design.

Eventually decided the bookmark feature wasn’t worth the complexity and removed all related code.

Search Function Not Working

Pagefind integration wasn’t working properly.

The causes were Pagefind script not loading at the right time and incorrect API usage.

Resolved by fixing dynamic script loading and using the correct API methods.

Summary

With these improvements, blog loading speed has significantly improved, and I think usability has gotten better too.

Image optimization in particular had a big effect, with noticeable differences even in perceived performance.

Including technical details, this became quite a large-scale renovation, but by solving problems one by one, I feel the blog has become better.

There still seem to be areas for improvement, but this feels like a good stopping point for now.

I’d like to continue nurturing this blog little by little.

Chat

Actually, Claude helped me with these improvements.

I don’t think I could have done such large-scale renovations alone.

It was especially helpful to get specific code suggestions for implementing each feature.

Developing with AI is quite enjoyable.

I wonder what I should improve next…

(This text was also created by Claude. Only this notice was written by a human)

How I Made My Blog Faster and More Feature-Rich

著者

semiramisu

公開日

2025 - 06 - 29

ライセンス CC BY-NC-SA 4.0

コメント