Tag: performance

  • Understanding ISR: The Secret to Fast Headless WordPress Sites

    Incremental Static Regeneration (ISR) is the killer feature that makes Next.js perfect for headless WordPress. But understanding when and how to use it can be tricky.

    What is ISR?

    ISR lets you update static pages after build time without rebuilding your entire site. You get the speed of static generation with the freshness of server-side rendering.

    Here’s how it works: Next.js generates static HTML at build time. After deployment, when someone requests a page, they get the cached version. In the background, Next.js regenerates the page and updates the cache.

    Time-Based vs On-Demand Revalidation

    Next.js offers two ISR strategies:

    Time-based revalidation regenerates pages after a specified interval:

    export const revalidate = 3600; // 1 hour

    This is great for content that updates predictably, like archive pages or dashboards.

    On-demand revalidation regenerates pages when triggered by an API call. This is what FlatWP uses. When you save a post in WordPress, our plugin immediately triggers revalidation:

    await fetch('/api/revalidate', {
      method: 'POST',
      body: JSON.stringify({ paths: ['/blog/my-post'] })
    });

    The FlatWP Approach

    We use different strategies for different content types:

    • Blog posts: On-demand ISR (update immediately when edited)
    • Static pages: No revalidation (fully static)
    • Archives: Short time-based ISR (5 minutes)
    • Homepage: Very short ISR or server component

    This gives you instant updates where they matter, without sacrificing performance.

    Performance Impact

    With ISR, first-time visitors get sub-100ms page loads. The page is pre-rendered, served from the edge, and cached globally. Subsequent visitors get even faster loads from CDN cache.

    Compare this to server-side rendering, which queries WordPress on every request. ISR gives you the best of both worlds.

  • Image Optimization in Headless WordPress: Beyond next/image

    WordPress is notorious for bloated images. Editors upload 5MB photos from their phone, and suddenly your site loads like it’s 2010.

    In headless WordPress, you have a unique opportunity to fix this at the framework level.

    The WordPress Media Problem

    Traditional WordPress generates multiple image sizes on upload. But these sizes are often wrong for modern responsive design. You end up with dozens of unused files cluttering your media library.

    Plus, WordPress doesn’t handle modern formats (WebP, AVIF) or responsive srcsets particularly well.

    The Next.js Solution

    Next.js Image component handles optimization automatically:

    • Serves modern formats (WebP/AVIF) to supporting browsers
    • Generates responsive srcsets on-demand
    • Lazy loads by default
    • Prevents layout shift with automatic sizing

    But you need to configure it correctly for WordPress.

    FlatWP’s Approach

    We create a custom image loader that:

    1. Pulls the original image from WordPress
    2. Proxies it through Next.js optimization
    3. Caches the optimized versions globally
    4. Serves from Vercel’s edge network

    The result? A 5MB WordPress upload becomes a 50KB WebP delivered in milliseconds.

    Configuration

    In your next.config.js:

    images: {
      domains: ['cms.flatwp.com/'],
      formats: ['image/avif', 'image/webp'],
      deviceSizes: [640, 750, 828, 1080, 1200, 1920],
    }

    Then use it in components:

    <Image
      src={post.featuredImage.url}
      alt={post.featuredImage.alt}
      width={1200}
      height={630}
      priority={isAboveFold}
    />

    WordPress-Side Optimization

    Even better: prevent bloated uploads in the first place. Our WordPress plugin includes:

    • Upload size limits
    • Automatic compression before upload
    • Warnings when images exceed recommended dimensions
    • Bulk optimization tools for existing media

    Performance Impact

    We measured a typical blog post with 5 images:

    • Traditional WordPress: 2.3MB transferred, 4.2s load
    • FlatWP optimized: 180KB transferred, 0.8s load

    That’s a 92% reduction in bandwidth and 5x faster load times. For free.

  • GraphQL vs REST API: Why We Chose GraphQL for FlatWP

    WordPress offers both REST API and GraphQL for headless implementations. We deliberately chose GraphQL for FlatWP, and here’s why.

    The Over-Fetching Problem

    WordPress REST API returns everything about a post, whether you need it or not:

    GET /wp-json/wp/v2/posts/123

    You get the author object, meta fields, embedded media, comment stats, and dozens of other fields you’ll never use. This bloats response sizes and slows down your site.

    GraphQL’s Precision

    With GraphQL, you request exactly what you need:

    query GetPost($id: ID!) {
      post(id: $id) {
        title
        content
        featuredImage {
          url
          alt
        }
      }
    }

    The response contains only those fields. Nothing more, nothing less.

    TypeScript Integration

    GraphQL’s typed schema enables automatic TypeScript generation. Our codegen process creates perfect types from your queries:

    // Auto-generated from GraphQL schema
    interface GetPostQuery {
      post: {
        title: string;
        content: string;
        featuredImage: {
          url: string;
          alt: string;
        };
      };
    }

    This is nearly impossible with REST API without manually maintaining types.

    Nested Data in One Request

    REST API requires multiple requests for nested data:

    // Get post
    GET /wp-json/wp/v2/posts/123
    // Get author
    GET /wp-json/wp/v2/users/5
    // Get categories
    GET /wp-json/wp/v2/categories?post=123

    GraphQL fetches everything in one query:

    query GetPost($id: ID!) {
      post(id: $id) {
        title
        author {
          name
          avatar
        }
        categories {
          name
          slug
        }
      }
    }

    Better Performance

    Fewer requests = faster page loads. We measured:

    • REST API: 3 requests, 45KB total, 280ms
    • GraphQL: 1 request, 12KB, 95ms

    The WPGraphQL Plugin

    WPGraphQL is mature, well-maintained, and has a huge ecosystem:

    • WPGraphQL for ACF
    • WPGraphQL for WooCommerce
    • WPGraphQL for Yoast SEO
    • WPGraphQL JWT Authentication

    Popular plugins have GraphQL extensions, making integration seamless.

    When to Use REST API

    GraphQL isn’t always the answer. Use REST API when:

    • You need file uploads (GraphQL doesn’t handle multipart well)
    • Your WordPress host doesn’t support WPGraphQL
    • You’re building a simple integration with minimal data needs

    But for full-featured headless sites, GraphQL’s benefits are undeniable.

  • Building a Search Experience Without Algolia

    Algolia is great, but $99/month for search on a small site feels excessive. FlatWP includes a fast, free alternative using static generation and client-side search.

    The Static Search Index Approach

    We generate a lightweight JSON index at build time:

    // app/search-index.json/route.ts
    export const revalidate = 3600;
    
    export async function GET() {
      const posts = await fetchAllPosts();
      
      const index = posts.map(post => ({
        id: post.id,
        title: post.title,
        excerpt: post.excerpt,
        slug: post.slug,
        category: post.category.name,
      }));
      
      return Response.json(index);
    }

    This creates a ~50KB JSON file (for 100 posts) that browsers cache.

    Client-Side Search with Fuse.js

    Fuse.js provides fuzzy search on the client:

    import Fuse from 'fuse.js';
    
    const fuse = new Fuse(searchIndex, {
      keys: ['title', 'excerpt', 'category'],
      threshold: 0.3,
      includeScore: true
    });
    
    const results = fuse.search(query);

    Search is instant – no network request needed.

    When Does This Break Down?

    This approach works well up to ~2000 posts. Beyond that:

    • Index size becomes noticeable (~300KB+)
    • Initial download impacts performance
    • Search slowdown on lower-end devices

    At that scale, consider upgrading to a search service.

    Enhancing the Experience

    We add keyboard shortcuts (⌘K), instant results as you type, and proper highlighting:

    <Command.Dialog>
      <Command.Input 
        placeholder="Search posts..."
        value={query}
        onValueChange={setQuery}
      />
      <Command.List>
        {results.map(result => (
          <Command.Item key={result.item.id}>
            <Link href={`/blog/${result.item.slug}`}>
              {highlightMatch(result.item.title, query)}
            </Link>
          </Command.Item>
        ))}
      </Command.List>
    </Command.Dialog>

    Progressive Enhancement

    For FlatWP Pro, we’re adding optional Algolia integration. It’s a feature flag:

    const searchProvider = process.env.SEARCH_PROVIDER || 'static';
    
    if (searchProvider === 'algolia') {
      // Use Algolia
    } else {
      // Use static JSON + Fuse.js
    }

    Start free, upgrade when needed. No lock-in.

    Performance Comparison

    We tested search on a 500-post site:

    • Static + Fuse.js: 15ms, 0 network requests
    • Algolia: 45ms average (includes network latency)
    • WordPress search: 300ms+ (full database query)

    The static approach is actually faster for most use cases.