Back to Blog
Development

Building Headless Drupal Applications with Druvance

A complete guide to decoupled Drupal architecture using JSON:API and modern frontend frameworks.

AR
Alex Rivera
Senior Frontend Developer
January 5, 2024
11 min read
headlessdecoupledapinextjs

Building Headless Drupal Applications with Druvance

Headless Drupal (or decoupled Drupal) separates the content management backend from the frontend presentation layer. This approach offers flexibility, performance, and modern development workflows.

What is Headless Drupal?

Traditional Drupal:

  • Backend and frontend tightly coupled
  • Drupal renders HTML
  • Limited frontend flexibility

Headless Drupal:

  • Backend serves content via API
  • Frontend is a separate application
  • Use any frontend technology

Why Go Headless?

Benefits

  • **Frontend Freedom** - Use React, Vue, Next.js, or any framework
  • **Better Performance** - Static site generation, client-side routing
  • **Omnichannel** - Serve content to web, mobile, IoT, etc.
  • **Developer Experience** - Modern tooling and workflows

Trade-offs

  • **Complexity** - Two systems to maintain
  • **Preview Challenges** - Content preview requires additional setup
  • **Module Compatibility** - Some Drupal modules won't work

Getting Started

Enable JSON:API

JSON:API is included in Drupal core:

# Enable the module
drush en jsonapi

# Verify it's working
curl https://yoursite.druvance.com/jsonapi

Configure CORS

Allow your frontend to access the API:

# services.yml
parameters:
  cors.config:
    enabled: true
    allowedOrigins: ['https://yourfrontend.com']
    allowedMethods: ['GET', 'POST', 'PATCH', 'DELETE']
    allowedHeaders: ['*']

Building the Frontend

Next.js Example

Create a modern React frontend:

// lib/drupal.ts
const DRUPAL_URL = process.env.NEXT_PUBLIC_DRUPAL_URL;

export async function getArticles() {
  const res = await fetch(`${DRUPAL_URL}/jsonapi/node/article`);
  return res.json();
}

// pages/index.tsx
export default function Home({ articles }) {
  return (
    <div>
      <h1>Latest Articles</h1>
      {articles.data.map(article => (
        <article key={article.id}>
          <h2>{article.attributes.title}</h2>
          <p>{article.attributes.body.value}</p>
        </article>
      ))}
    </div>
  );
}

export async function getStaticProps() {
  const articles = await getArticles();
  return { props: { articles } };
}

Authentication

For protected content, use OAuth:

// Get access token
const token = await fetch(`${DRUPAL_URL}/oauth/token`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'password',
    client_id: 'your-client-id',
    username: 'user',
    password: 'pass',
  }),
});

// Use token in requests
const articles = await fetch(`${DRUPAL_URL}/jsonapi/node/article`, {
  headers: {
    'Authorization': `Bearer ${token.access_token}`,
  },
});

Content Modeling

Structured Content

Design content types for API consumption:

Article
├── Title (text)
├── Body (formatted text)
├── Author (entity reference)
├── Tags (taxonomy)
├── Featured Image (media)
└── Published Date (datetime)

Custom Fields

Create API-friendly field types:

// Custom field formatter
namespace DrupalmymodulePluginFieldFieldFormatter;

use DrupalCoreFieldFormatterBase;

/**
 * @FieldFormatter(
 *   id = "api_image_formatter",
 *   label = @Translation("API Image Formatter"),
 *   field_types = {"image"}
 * )
 */
class ApiImageFormatter extends FormatterBase {
  public function viewElements($items, $langcode) {
    $elements = [];
    foreach ($items as $delta => $item) {
      $elements[$delta] = [
        'url' => $item->entity->createFileUrl(),
        'alt' => $item->alt,
        'width' => $item->width,
        'height' => $item->height,
      ];
    }
    return $elements;
  }
}

Advanced Patterns

Incremental Static Regeneration

Keep content fresh with ISR:

// Next.js ISR
export async function getStaticProps() {
  const articles = await getArticles();
  return {
    props: { articles },
    revalidate: 60, // Regenerate every 60 seconds
  };
}

On-demand Revalidation

Trigger rebuilds when content changes:

// Drupal module
function mymodule_node_update($node) {
  // Call frontend revalidation endpoint
  Drupal::httpClient()->post(
    'https://frontend.com/api/revalidate',
    ['json' => ['path' => '/node/' . $node->id()]]
  );
}

GraphQL Alternative

For complex queries, consider GraphQL:

# Install GraphQL module
composer require drupal/graphql

# Example query
query {
  articles(limit: 10) {
    title
    author {
      name
      picture
    }
    tags
    published
  }
}

Performance Optimization

CDN Caching

Cache API responses at the edge:

# Cloudflare Page Rule
URL: */jsonapi/*
Cache Level: Cache Everything
Edge Cache TTL: 1 hour

Response Optimization

Use sparse fieldsets to reduce payload:

# Only get the fields you need
/jsonapi/node/article?fields[node--article]=title,body

Pagination

Handle large result sets:

# Paginated requests
/jsonapi/node/article?page[limit]=10&page[offset]=0
/jsonapi/node/article?page[limit]=10&page[offset]=10

Deployment on Druvance

Multi-site Setup

Deploy frontend and backend together:

druvance.yml
sites:
  backend:
    type: drupal
    docroot: backend/web
  frontend:
    type: node
    docroot: frontend
    build: npm run build
    start: npm start

Environment Variables

Configure different environments:

# .env.development
NEXT_PUBLIC_DRUPAL_URL=https://backend.druvance.dev

# .env.production
NEXT_PUBLIC_DRUPAL_URL=https://backend.druvance.com

Common Challenges

Preview Mode

Implement content preview:

// Preview API route
export default async function handler(req, res) {
  const { slug, token } = req.query;
  
  // Verify preview token
  const previewData = await verifyToken(token);
  
  // Fetch draft content
  const article = await getDraftArticle(slug, previewData);
  
  res.setPreviewData(previewData);
  res.redirect(`/articles/${slug}`);
}

SEO

Handle SEO in headless architecture:

  • Use Next.js for server-side rendering
  • Implement meta tags dynamically
  • Generate sitemaps from Drupal content
  • Handle redirects in frontend

Conclusion

Headless Drupal offers powerful flexibility for modern web applications. With Druvance's support for both Drupal and Node.js applications, building decoupled architectures has never been easier.

Ready to go headless? [Start your free trial today.](https://druvance.com/signup)

Share this article:
AR

Written by Alex Rivera

Senior Frontend Developer

Alex Rivera is part of the Druvance team, helping developers and organizations build better Drupal experiences. Follow for more insights on Drupal, web development, and cloud hosting.

Subscribe to our newsletter

Get the latest articles, tutorials, and updates delivered to your inbox.