Category: Performance

  • Image Optimization in Headless WordPress: Beyond next/image

    Full-Stack TypeScript: WordPress Meets Next.js

    TypeScript transformed our Next.js + WordPress setup. Here’s everything we learned shipping type-safe headless sites for real clients with zero compromises maximum confidence.

    TypeScript code in IDE

    The Promise of TypeScript

    When WordPress meets Next.js, you’re dealing with two systems that speak different languages. WordPress returns PHP-shaped data; Next.js expects JavaScript objects. TypeScript bridges this gap with compile-time safety.

    “Without types, you catch bugs in production. With types, you catch them before your first commit.” – Every developer who’s been burned by runtime errors

    Type Generation from GraphQL

    The magic happens when you generate TypeScript types directly from your GraphQL schema. Here’s how we do it with graphql-codegen:

    // codegen.yml
    schema: 'https://cms.flatwp.com/graphql'
    generates:
      src/types/graphql.ts:
        plugins:
          - 'typescript'
          - 'typescript-operations'
          - 'typed-document-node'
        config:
          skipTypename: false
          withHooks: true
          withComponent: false
    

    Now every GraphQL query in your Next.js app is fully typed. When WordPress admins add fields, developers get instant type errors if components aren’t updated. Check out the GraphQL Code Generator docs for more details.

    Developer reviewing code

    Example: Typed Post Query

    Here’s what a typical WordPress post query looks like with TypeScript:

    import { useQuery } from '@apollo/client';
    import { GetPostDocument, GetPostQuery } from '@/types/graphql';
    
    interface PostPageProps {
      slug: string;
    }
    
    export function PostPage({ slug }: PostPageProps) {
      const { data, loading, error } = useQuery<GetPostQuery>(
        GetPostDocument,
        { variables: { slug } }
      );
    
      if (loading) return <Skeleton />;
      if (error) return <ErrorBoundary error={error} />;
      
      const post = data?.post;
      if (!post) return <NotFound />;
    
      // TypeScript knows exactly what properties exist!
      return (
        <article>
          <h1>{post.title}</h1>
          <time dateTime={post.date}>{formatDate(post.date)}</time>
          <div dangerouslySetInnerHTML={{ __html: post.content }} />
        </article>
      );
    }

    Advanced Type Patterns

    Here are some advanced TypeScript patterns we use throughout FlatWP:

    1. Discriminated Unions – Perfect for ACF flexible content blocks
    2. Mapped Types – Transform WP data structures safely
    3. Conditional Types – Handle optional ACF fields elegantly
    4. Template Literal Types – Type-safe route generation

    Code on multiple monitors

    Discriminated Union Example

    Here’s how we handle flexible content blocks with type safety:

    type ContentBlock =
      | { type: 'hero'; heading: string; image: WPImage }
      | { type: 'features'; items: Feature[] }
      | { type: 'testimonial'; quote: string; author: string }
      | { type: 'cta'; text: string; url: string };
    
    function renderBlock(block: ContentBlock) {
      switch (block.type) {
        case 'hero':
          // TypeScript knows block.heading and block.image exist
          return <HeroBlock heading={block.heading} image={block.image} />;
        case 'features':
          // TypeScript knows block.items exists
          return <FeaturesGrid items={block.items} />;
        // ... other cases
      }
    }
    Utility Types for WordPress

    We’ve created several utility types that make working with WordPress data much cleaner. These are included in the FlatWP starter:

    • WPImage – Standardized image object with URL, alt, dimensions
    • WPPost<T> – Generic post type with custom field support
    • WPCategory – Taxonomy term with full hierarchy
    • WPMenu – Navigation menu with nested items

    Strict Mode Configuration

    We run TypeScript in strict mode with additional checks enabled:

    {
      "compilerOptions": {
        "strict": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,
        "forceConsistentCasingInFileNames": true,
        "isolatedModules": true
      }
    }

    This configuration catches edge cases that would otherwise slip through. Yes, it requires more upfront work, but the elimination of runtime errors is worth it.

    Key principle: Make invalid states unrepresentable. If WordPress can’t return null for a required field, your TypeScript types shouldn’t allow null either.

    Handling WordPress Nullability

    WordPress is infamous for returning null or undefined unexpectedly. Here’s how we handle it:

    // Bad: Optimistic typing
    interface Post {
      title: string;  // Might actually be null!
      content: string; // Might be empty string OR null
    }
    
    // Good: Defensive typing
    interface Post {
      title: string | null;
      content: string | null;
    }
    
    // Better: Use type guards
    function isValidPost(post: Post): post is Required<Post> {
      return post.title !== null && post.content !== null;
    }
    
    // Usage
    if (isValidPost(post)) {
      // TypeScript knows title and content are strings
      return <h1>{post.title}</h1>;
    }

    TypeScript error checking

    Testing Strategy

    TypeScript dramatically improves our testing strategy. Here’s what we test:

    Test Type Coverage Tools Frequency
    Type Checking 100% tsc –noEmit Pre-commit
    Unit Tests 80%+ Vitest PR
    Integration Critical paths Playwright Pre-deploy
    Type Coverage 95%+ type-coverage Weekly

    Example Test Suite

    Here’s how we test a typed WordPress component:

    import { describe, it, expect } from 'vitest';
    import { render, screen } from '@testing-library/react';
    import { PostCard } from './PostCard';
    import type { Post } from '@/types/graphql';
    
    describe('PostCard', () => {
      it('renders post with all required fields', () => {
        const post: Post = {
          id: '1',
          title: 'Test Post',
          excerpt: 'Test excerpt',
          date: '2024-01-01',
          author: { name: 'John Doe' },
          featuredImage: {
            node: {
              sourceUrl: '/test.jpg',
              altText: 'Test image',
            },
          },
        };
    
        render(<PostCard post={post} />);
        
        expect(screen.getByText('Test Post')).toBeInTheDocument();
        expect(screen.getByAltText('Test image')).toBeInTheDocument();
      });
    
      it('handles missing optional fields gracefully', () => {
        const post: Post = {
          id: '2',
          title: 'Minimal Post',
          excerpt: null,  // TypeScript allows this
          date: '2024-01-01',
          author: { name: 'Jane Doe' },
          featuredImage: null,  // Also allowed
        };
    
        render(<PostCard post={post} />);
        expect(screen.getByText('Minimal Post')).toBeInTheDocument();
      });
    });

    Real-World Performance Impact

    After implementing strict TypeScript across 10+ client projects, we measured the impact:

    • 90% reduction in production runtime errors
    • 40% faster feature development (thanks to autocomplete + refactoring)
    • Zero “undefined is not a function” errors in 6 months
    • 50% less time spent in code review catching type issues

    The upfront investment in TypeScript configuration pays for itself within the first sprint. Don’t skip this step!

    Team celebrating success

    Resources & Next Steps

    Want to dive deeper? Here are our recommended resources:

    1. Read the official TypeScript handbook
    2. Explore WPGraphQL documentation
    3. Check out the FlatWP repository for examples
    4. Join our Discord community for help

    Conclusion

    TypeScript + WordPress + Next.js is a powerful combination. With proper type safety, you get the flexibility of WordPress with the developer experience of modern React development.

    The FlatWP starter includes all of this configuration out-of-the-box, so you can focus on building features instead of fighting with types. Ship faster. Ship safer.

  • SEO in Headless WordPress: Better Than Traditional?

    One concern we hear about headless WordPress: “What about SEO?”

    SEO analytics and performance metrics

    The truth is, headless WordPress can be better for SEO than traditional WordPress. Here’s why.

    Performance is an SEO Factor

    Google’s Core Web Vitals directly impact rankings. FlatWP’s static/ISR approach delivers:

    • LCP < 1s: Pages load in under a second (vs 3-5s for traditional WP)
    • CLS near 0: No layout shift from lazy-loaded elements
    • FID < 50ms: Instant interactivity

    These metrics give you a ranking advantage over slower traditional sites.

    Server-Side Rendering

    Unlike single-page apps that struggle with SEO, Next.js renders full HTML on the server. Crawlers see complete, rendered pages – no JavaScript execution required.

    This means:

    • Content is immediately available to bots
    • Social media crawlers see proper Open Graph data
    • No SEO penalties for client-side rendering

    Meta Data from WordPress

    FlatWP pulls SEO metadata directly from Yoast or Rank Math:

    export async function generateMetadata({ params }) {
      const post = await fetchPost(params.slug);
      
      return {
        title: post.seo.title,
        description: post.seo.metaDesc,
        openGraph: {
          images: [post.seo.opengraphImage],
        },
      };
    }

    Editors manage SEO in WordPress. Next.js renders it perfectly.

    Automatic Sitemaps

    FlatWP generates XML sitemaps dynamically from WordPress content:

    export default async function sitemap() {
      const posts = await fetchAllPosts();
      
      return posts.map(post => ({
        url: `https://flatwp.com/blog/${post.slug}`,
        lastModified: post.modifiedDate,
      }));
    }

    When content updates, the sitemap updates. Search engines stay in sync.

    Schema Markup

    We include proper JSON-LD schema for articles:

    {
      "@context": "https://schema.org",
      "@type": "Article",
      "headline": post.title,
      "datePublished": post.date,
      "author": {
        "@type": "Person",
        "name": post.author.name
      }
    }

    This helps Google understand your content structure.

    No Bloat

    Traditional WordPress sites load unnecessary plugins, tracking scripts, and theme bloat. This slows everything down.

    With FlatWP, you control exactly what JavaScript loads. Most pages need zero client-side JS for content display.

    The Bottom Line

    Headless WordPress with Next.js is better for SEO than traditional WordPress because:

    1. Faster page loads = better rankings
    2. Perfect SSR = happy crawlers
    3. Clean HTML = no bloat penalty
    4. Modern image formats = faster LCP

    You get WordPress’s content management with Next.js’s performance. That’s an SEO win-win.