Building Modern Websites with Headless WordPress, Next.js & Elementor

Did you know that 71% of developers prefer using headless CMS’ for their flexibility and performance? This comprehensive guide will show you how to leverage headless WordPress with Next.js alongside Elementor to build fast, modern websites with powerful user interfaces.
What is Headless WordPress?
Headless WordPress is a modern approach where the front end is decoupled from the back end. WordPress serves as a content management system (CMS) while frameworks like Next.js render the front end. This setup allows developers to create dynamic websites with enhanced performance, scalability & flexibility.
Key Components:
- WordPress: Acts as the backend CMS, allowing content creators to manage posts, pages & media effortlessly.
- Next.js: React-based framework that provides server-side rendering (SSR), static site generation (SSG) & seamless routing for faster performance.
- Elementor: A powerful page builder for WordPress that enables users to design complex layouts and manage content through a visual interface.
Implement Headless WordPress with Next.js & Elementor Step-by-Step
1. WordPress Setup
Install Elementor. Make sure the Elementor plugin is installed and active in your WordPress instance. Enable headless functionality: You can enable REST API or GraphQL support using WPGraphQL.
Example to enable REST API:
// functions.php - Enable REST API for custom post types
add_action('init', 'register_custom_post_types');
function register_custom_post_types() {
register_post_type('custom', array(
'show_in_rest' => true,
'supports' => ['elementor'],
));
}
Install plugins:
- WPGraphQL – maps Elementor data to GraphQL.
2. Elementor Configuration
- Build Pages: Design your pages normally using Elementor.
- Dynamic Content: Utilize Elementor’s dynamic tags for CMS-driven content like post titles and featured images.
3. Next.js Setup
Create a Next.js App:
npx create-next-app@latest
Install Dependencies:
npm install graphql @apollo/client @headlessui/react
4. Data Fetching
Option A: GraphQL (Recommended)
Configure Apollo Client (lib/apollo-client.js
):
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://your-site.com/graphql',
cache: new InMemoryCache(),
});
Fetch Elementor Page (pages/[slug].js
):
import { gql, useQuery } from '@apollo/client';
const GET_PAGE = gql`
query GetPage($id: ID!) {
page(id: $id, idType: URI) {
elementorData
title
}
}
`;
export default function Page({ slug }) {
const { data } = useQuery(GET_PAGE, { variables: { id: slug } });
const page = data?.page;
if (!page) return <div>Loading...</div>;
return (
<div>
<h1>{page.title}</h1>
<ElementorRenderer data={page.elementorData} />
</div>
);
}
Option B: REST API
Fetch Rendered HTML (pages/[slug].js
):
export async function getStaticProps({ params }) {
const res = await fetch(
`https://your-site.com/wp-json/wp/v2/pages?slug=${params.slug}&_fields=id,title,elementor_content`
);
const [page] = await res.json();
return { props: { page } };
}
5. Render Elementor Content
Elementor Renderer Component (components/ElementorRenderer.js
):
const ElementorRenderer = ({ data }) => {
const parsedData = JSON.parse(data);
const renderElements = (elements) =>
elements.map((el) => {
switch (el.elType) {
case 'section':
return <section key={el.id}>{renderElements(el.elements)}</section>;
case 'column':
return <div key={el.id}>{renderElements(el.elements)}</div>;
case 'widget':
return renderWidget(el);
default:
return null;
}
});
const renderWidget = (widget) => {
switch (widget.widgetType) {
case 'heading':
return <h2>{widget.settings.title}</h2>;
case 'image':
return <img src={widget.settings.url} alt={widget.settings.alt} />;
// Add more widget cases
default:
return <div>Unsupported widget</div>;
}
};
return <>{renderElements(parsedData)}</>;
};
6. Styling
Global CSS: Include Elementor’s CSS in pages/_app.js
:
import 'elementor-frontend/css/frontend.min.css';
Per-Page CSS: Inject page-specific CSS using metadata retrieved from the REST API.
7. Deployment & Optimisation
Static Site Generation (SSG):
export async function getStaticPaths() {
const res = await fetch('https://your-site.com/wp-json/wp/v2/pages?_fields=slug');
const pages = await res.json();
const paths = pages.map((page) => ({ params: { slug: page.slug } }));
return { paths, fallback: true };
}
Preview Mode: Implement Next.js preview mode for displaying draft content.
Key Considerations:
- Elementor Data Structure: Element stores its data as JSON; ensure proper mapping of widget types to corresponding React components.
- Dynamic Content: Utilize @apollo/client for real-time updates.
- Performance: Optimize rendering by pre-fetching data with getStaticProps or getServerSideProps.
Frequently Asked Questions
Q: What are the benefits of using headless WordPress?
A: The primary benefits include improved performance, greater flexibility in frontend development using modern frameworks like Next.js, and enhanced security by decoupling the frontend from the backend.
Q: Can I still use Elementor with a headless setup?
A: Yes, while Elementor is primarily designed for traditional WordPress setups, you can still utilize Elementor for designing your pages, and extract the content through the REST API or WPGraphQL.
Q: What if I need to use custom components?
A: You can create custom React components in Next.js that correspond to Elementor widgets, allowing you to leverage the design capabilities of Elementor while adapting them for a React-based frontend.
Key Takeaways & Next Steps
Set up WordPress with Elementor and the required plugins. Create your Next.js project and configure data fetching through GraphQL or REST. Implement custom rendering components to display Elementor content in Next.js.
Recommended Actions:
Get started with your WordPress setup today.
Experiment with creating and rendering custom components.
Explore Next.js performance enhancements for your site.
This structured approach combines the power of WordPress, Next.js, and Elementor, giving you a robust solution for modern web development.