Building Flexible, High-Performance Data-Driven Components with WordPress and Next.js

As a full-stack developer who has worked extensively with decoupled WordPress architectures, I‘ve found that pairing WordPress with Next.js is a powerful way to build data-driven components. It provides an ideal balance of flexibility for content editors and performance and maintainability for developers.

In this post, I‘ll walk through how to build a data-driven component using WordPress as a headless CMS and Next.js for the frontend. I‘ll share some architectural guidance and lessons learned from my experience to help you make the most of this stack.

Why Use WordPress as a Headless CMS?

WordPress is a great choice for a headless CMS because it provides a familiar and intuitive content editing experience out of the box. With the shift towards Jamstack architectures, using WordPress as a headless CMS has become increasingly popular.

According to a 2021 survey by Kentico, 49% of developers reported using a headless CMS, and among those, WordPress was the most popular choice at 63% (source). By exposing its data through a GraphQL or REST API, WordPress can serve as a content repository for a variety of frontend frameworks.

Some of the key benefits of using WordPress as a headless CMS include:

  • Content editors can use the familiar WordPress admin interface
  • Developers can choose their preferred frontend stack
  • Frontend and backend can be developed and deployed independently
  • The frontend can be optimized for performance without WordPress constraints
  • WordPress plugins can still be used to extend content editing capabilities

When paired with Next.js for the frontend, you get the added benefits of:

  • Server-side rendering and static site generation for excellent performance
  • A great developer experience with React and the Next.js framework
  • Easy integration with WordPress through GraphQL
  • Incremental static regeneration and preview mode for a great editing experience

Structuring WordPress Custom Fields for Maximum Flexibility

When building data-driven components with WordPress, it‘s important to structure your custom fields in a way that maximizes flexibility and reusability. That‘s where Advanced Custom Fields (ACF) comes in.

ACF provides an intuitive interface for adding custom fields to WordPress content types. When planning out your fields, consider:

  • What are the discrete pieces of data that make up the component?
  • Which fields might be reused across multiple components?
  • How can you structure the fields to minimize redundancy?

For our example component, we have fields for:

  • Title
  • Description
  • 4 related posts
  • 2 call-to-action buttons

To keep things modular, we can create a "Related Post" field group that includes fields for the post title, excerpt, featured image, and URL. This field group can then be reused in other components.

For the call-to-action buttons, we can create a "Button" field group with label and URL fields. This can also be reused throughout the site.

Here‘s an example of how you might structure the fields in ACF:

- Component (Field Group)
  - Title (Text field)
  - Description (Textarea field)
  - Related Posts (Repeater field)
    - Post (Post Object field)
  - Buttons (Repeater field) 
    - Button (Group field)
      - Label (Text field)
      - URL (URL field)

By using repeater and group fields, we can create modular, reusable field structures. This makes our component more flexible and easily adaptable to different use cases.

Optimizing GraphQL Queries for Performance

Once our WordPress fields are set up, we need to expose that data through a GraphQL API. WPGraphQL is a great plugin for this. It automatically detects your ACF fields and makes them available in the schema.

However, it‘s important to be mindful of overfetching when querying data from WordPress. Overfetching occurs when we request more data than we actually need for a given component. This can slow down the API response and negatively impact frontend performance.

To avoid overfetching, we should carefully craft our GraphQL queries to request only the necessary fields. For example, instead of querying for the entire Post object for our related posts, we can specify just the title, excerpt, featuredImage, and uri fields:

query GetPageById($id: ID!) {
  page(id: $id) {
    ACF_Fields {
      relatedPosts {
        post {
          title
          excerpt
          featuredImage {
            node {
              sourceUrl
            }
          }
          uri
        }
      }
    }
  }
}

By being selective about the fields we query, we can minimize the amount of data transferred over the network and speed up our component rendering.

Organizing Next.js Code for Data-Driven Components

When it comes to actually building the Next.js frontend for our data-driven component, it‘s important to consider how to organize the code for maximum maintainability.

I like to create a dedicated directory for each component, including:

  • The main component file (e.g. OurComponent.js)
  • A GraphQL query file (e.g. OurComponent.query.js)
  • A styles file if using CSS modules (e.g. OurComponent.module.css)

This keeps all the code related to a given component co-located and easy to find.

In the main component file, we can use Apollo Client‘s useQuery hook to fetch the data from WordPress:

import { useQuery } from ‘@apollo/client‘;
import { GET_PAGE_BY_ID } from ‘./OurComponent.query‘;

export default function OurComponent({ id }) {
  const { data, loading, error } = useQuery(GET_PAGE_BY_ID, {
    variables: { id },
  });

  if (loading) return <div>Loading...</div>; 
  if (error) return <div>Error: {error.message}</div>;

  const { ACF_Fields } = data.page;

  return (
    // Render the component using the ACF fields data
  );
}

By keeping the GraphQL query in a separate file, we can easily reuse it in other parts of the application if needed.

For styling the component, I recommend using a CSS-in-JS solution like styled-components or CSS modules. This keeps the styles scoped to the component and avoid global style conflicts.

Advanced Techniques for an Optimal Editing Experience

One of the great things about Next.js is that it provides some advanced features out of the box that can greatly enhance the editing experience when paired with WordPress.

Two key features are incremental static regeneration (ISR) and preview mode.

With ISR, we can statically generate our pages at build time for maximum performance, but also provide a way to update them in the background after the initial build. This means editors can publish updates to WordPress and have them reflected on the live site without a full rebuild.

To enable ISR, we can use the getStaticProps function in our Next.js page component:

export async function getStaticProps({ params }) {
  const { data } = await client.query({
    query: GET_PAGE_BY_ID,
    variables: { id: params.id },
  });

  return {
    props: {
      page: data.page,
    },
    revalidate: 60, // Regenerate the page every 60 seconds
  };
}

The revalidate property tells Next.js to regenerate the page in the background every 60 seconds if there are any updates.

Preview mode, on the other hand, allows content editors to preview their changes in the context of the actual frontend before publishing. Next.js provides an API route that can be called to enable preview mode and a getStaticProps hook to fetch draft content.

Here‘s an example of how to set up preview mode:

// pages/api/preview.js

export default async function handler(req, res) {
  const { secret, id, slug } = req.query;

  if (secret !== process.env.WORDPRESS_PREVIEW_SECRET) {
    return res.status(401).json({ message: ‘Invalid secret‘ });
  }

  res.setPreviewData({ id, slug });
  res.redirect(`/${slug}`);
}
// pages/[slug].js

export async function getStaticProps({ params, previewData }) {
  const { data } = await client.query({
    query: GET_PAGE_BY_ID,
    variables: {
      id: previewData?.id || params.id,
    },
  });

  return {
    props: {
      page: data.page,
    },
  };
}

By providing these preview and incremental update capabilities, we can create a great editing experience for content editors while still enjoying the performance benefits of static site generation.

Conclusion and Key Takeaways

Building data-driven components with WordPress and Next.js is a powerful approach that combines the flexibility of a headless CMS with the performance and developer experience of a modern frontend framework.

Some of the key takeaways from this post:

  • Structure your WordPress custom fields in a modular, reusable way with ACF
  • Carefully design your GraphQL queries to avoid overfetching and optimize performance
  • Organize your Next.js component code in a maintainable way, separating concerns like data fetching and styling
  • Leverage Next.js features like ISR and preview mode for an optimal editing experience

By following these best practices and architectural patterns, you can create data-driven components that are flexible, performant, and easy to maintain. The combination of WordPress and Next.js is a great foundation for modern, decoupled web applications.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *