The future of Drupal is headless
Next.js for Drupal has everything you need to build a next-generation front-end for your Drupal site.
Everything you expect from Drupal.
On a modern stack.
Go headless without compromising features.
Seamless Editing
Inline preview built-in to the editing interface.
Instant Publishing
New content and updates are live instantly.
Multi-site
Power multiple Next.js sites from one Drupal site.
Authentication
Authentication with support for roles and permissions.
Webforms
Built React forms backed by the Webform module.
Search API
Support for decoupled faceted search powered by Search API.
Internationalization
Built-in translation and Automatic Language detection.
Performance
Deploy and scale your sites via content delivery networks.
Security
Protect your site from attacks by separating code from the interface.
Out-of-the-box tooling for the best developer experience
Build all the features you need. Faster.
import { getPathsFromContext, getResourceFromContext } from "next-drupal"
export default function ArticleNodePage({ node }) {
return node ? (
<div>
<h1>{node.title}</h1>
</div>
) : null
}
export async function getStaticPaths(context) {
return {
paths: await getPathsFromContext("node--article", context),
fallback: false,
}
}
export async function getStaticProps(context) {
const node = await getResourceFromContext("node--article", context)
return {
props: {
node,
},
}
}
import { getMenu } from "next-drupal"
export async function getStaticProps() {
// Fetch the main menu.
return {
props: {
menu: await getMenu("main"), // highlight-line,
},
}
}
import Link from "next/link"
import { useMenu } from "next-drupal"
export default function Page({ node }) {
const { tree } = useMenu("main") // highlight-line
return (
<ul>
{tree?.map((item) => (
<li key={item.id}>
<Link href={item.url} passHref>
<a>{item.title}</a>
</Link>
</li>
))}
</ul>
)
}
import { getResource } from "next-drupal"
// Get the `es` translation for a page by uuid.
const node = await getResource(
"node--page",
"07464e9f-9221-4a4f-b7f2-01389408e6c8",
{
locale: "es",
defaultLocale: "en",
}
)
import { getMenu } from "next-drupal"
export async function getStaticProps(context) {
// Get translated menu from context.
const menu = await getMenu("main", context)
return {
props: {
menu,
},
}
}
import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
export default NextAuth({
providers: [
CredentialsProvider({
name: "Credentials",
async authorize(credentials) {
const formData = new URLSearchParams()
formData.append("grant_type", "password")
formData.append("username", credentials.username)
formData.append("password", credentials.password)
// Get access token from Drupal.
const response = await fetch(
`${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}/oauth/token`,
{
method: "POST",
body: formData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}
)
const data = await response.json()
if (response.ok && data?.access_token) {
return data
}
return null
},
}),
],
})
export default async function handler(request, response) {
try {
await fetch(
`${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}/webform_rest/submit`,
{
method: "POST",
body: JSON.stringify({
webform_id: request.query.form,
name: request.body.name,
}),
}
)
response.status(200).end()
} catch (error) {
return response.status(400).json(error.message)
}
}
export default function ContactPage() {
async function handleSubmit() {
await fetch(`/api/forms/contact`, {
method: "POST",
body: JSON.stringify({
name: event.target.name.value,
}),
})
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Name</label>
<input type="text" id="name" name="name" />
<button type="submit">Submit</button>
</form>
)
}
import { getSearchIndex } from "next-drupal"
export default async function handler(request, response) {
try {
const body = JSON.parse(request.body)
const results = await getSearchIndex(request.query.index, body)
response.json(results)
} catch (error) {
return response.status(400).json(error.message)
}
}
export default function SearchPage() {
const [results, setResults] = React.useState<>([])
async function handleSubmit() {
const response = await fetch("/api/search/articles", {
method: "POST",
body: JSON.stringify({
params: {
filter: {
fulltext: event.target.keywords.value,
},
},
}),
})
const results = await response.json()
setResults(results)
}
return (
<form onSubmit={handleSubmit}>
<input type="search" name="keywords" />
<button type="submit">Search</button>
</form>
)
}
import {
getPathsFromContext,
getResourceCollectionFromContext,
DrupalNode,
} from "next-drupal"
interface BlogPageProps {
nodes: DrupalNode[]
}
export default function BlogPage({ nodes }: BlogPageProps) {
return ...
}
export async function getStaticProps(
context
): Promise<GetStaticPropsResult<BlogPageProps>> {
const nodes = await getResourceCollectionFromContext<DrupalNode[]>(
"node--article",
context
)
return {
props: {
nodes,
},
}
}