├── .env.example ├── .eslintrc.json ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── CHANGELOG.md ├── README.md ├── app ├── [...slug] │ └── page.tsx ├── api │ ├── disable-draft │ │ └── route.ts │ ├── draft │ │ └── route.ts │ └── revalidate │ │ └── route.ts ├── layout.tsx └── page.tsx ├── components ├── drupal │ ├── Article.tsx │ ├── ArticleTeaser.tsx │ └── BasicPage.tsx ├── misc │ └── DraftAlert │ │ ├── Client.tsx │ │ └── index.tsx └── navigation │ ├── HeaderNav.tsx │ └── Link.tsx ├── lib ├── drupal.ts └── utils.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── robots.txt ├── styles └── globals.css ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # See https://next-drupal.org/docs/environment-variables 2 | 3 | # Required 4 | NEXT_PUBLIC_DRUPAL_BASE_URL=https://site.example.com 5 | NEXT_IMAGE_DOMAIN=site.example.com 6 | 7 | # Authentication 8 | DRUPAL_CLIENT_ID=Retrieve this from /admin/config/services/consumer 9 | DRUPAL_CLIENT_SECRET=Retrieve this from /admin/config/services/consumer 10 | 11 | # Required for On-demand Revalidation 12 | DRUPAL_REVALIDATE_SECRET=Retrieve this from /admin/config/services/next 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # IDE files 24 | /.idea 25 | /.vscode 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore everything. 2 | /* 3 | 4 | # Format most files in the root directory. 5 | !/*.js 6 | !/*.ts 7 | !/*.md 8 | !/*.json 9 | # But ignore some. 10 | /package.json 11 | /package-lock.json 12 | /CHANGELOG.md 13 | 14 | # Don't ignore these nested directories. 15 | !/app 16 | !/components 17 | !/lib 18 | !/pages 19 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "es5" 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [1.8.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.7.2...basic-starter@1.8.0) (2022-12-06) 7 | 8 | **Note:** Version bump only for package basic-starter 9 | 10 | 11 | 12 | 13 | 14 | ## [1.7.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.7.1...basic-starter@1.7.2) (2022-12-06) 15 | 16 | **Note:** Version bump only for package basic-starter 17 | 18 | 19 | 20 | 21 | 22 | ## [1.7.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.7.0...basic-starter@1.7.1) (2022-09-07) 23 | 24 | **Note:** Version bump only for package basic-starter 25 | 26 | 27 | 28 | 29 | 30 | # [1.7.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.6.0...basic-starter@1.7.0) (2022-07-29) 31 | 32 | 33 | ### Features 34 | 35 | * **basic-starter:** add type to next.config.js ([c1db60d](https://github.com/chapter-three/next-drupal/commit/c1db60d460ec2c0b2f3149d455e8c1b4bcc4a080)) 36 | * **basic-starter:** fix jsonapi params to work with vanilla drupal ([258019f](https://github.com/chapter-three/next-drupal/commit/258019f5bc0fa34e3ce3a824f99b28ea60b5ad30)) 37 | * **basic-starter:** update the example env variables ([1ed83da](https://github.com/chapter-three/next-drupal/commit/1ed83da4c0ec6ef3f0487f43faf1d8a4fdb29858)) 38 | 39 | 40 | 41 | 42 | 43 | # [1.6.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.6.0-rc.0...basic-starter@1.6.0) (2022-06-14) 44 | 45 | **Note:** Version bump only for package basic-starter 46 | 47 | 48 | 49 | 50 | 51 | # [1.6.0-rc.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.3-rc.1...basic-starter@1.6.0-rc.0) (2022-06-14) 52 | 53 | 54 | ### Features 55 | 56 | * **basic-starter:** update to DrupalClient ([e2dc220](https://github.com/chapter-three/next-drupal/commit/e2dc2202d01a09aba5695ffbbe35d990981d3301)) 57 | 58 | 59 | 60 | 61 | 62 | ## [1.5.3-rc.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.3-rc.0...basic-starter@1.5.3-rc.1) (2022-06-10) 63 | 64 | **Note:** Version bump only for package basic-starter 65 | 66 | 67 | 68 | 69 | 70 | ## [1.5.3-rc.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.3-alpha.0...basic-starter@1.5.3-rc.0) (2022-06-06) 71 | 72 | **Note:** Version bump only for package basic-starter 73 | 74 | 75 | 76 | 77 | 78 | ## [1.5.3-alpha.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.2...basic-starter@1.5.3-alpha.0) (2022-06-02) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * **basic-starter:** rename api pages to .ts ([40456b0](https://github.com/chapter-three/next-drupal/commit/40456b08ae288c441195fe38b8d5008736bfce05)) 84 | 85 | 86 | 87 | 88 | 89 | ## [1.5.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.1...basic-starter@1.5.2) (2022-05-02) 90 | 91 | **Note:** Version bump only for package basic-starter 92 | 93 | 94 | 95 | 96 | 97 | ## [1.5.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.5.0...basic-starter@1.5.1) (2022-04-25) 98 | 99 | **Note:** Version bump only for package basic-starter 100 | 101 | 102 | 103 | 104 | 105 | # [1.5.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.4.1-rc.0...basic-starter@1.5.0) (2022-04-19) 106 | 107 | 108 | ### Bug Fixes 109 | 110 | * **basic-starter:** rename eslint.json ([844bd93](https://github.com/chapter-three/next-drupal/commit/844bd93b0d4e6a3d24e6e76622067f344e440def)) 111 | 112 | 113 | 114 | 115 | 116 | ## [1.4.1-rc.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.4.1-alpha.0...basic-starter@1.4.1-rc.0) (2022-04-19) 117 | 118 | **Note:** Version bump only for package basic-starter 119 | 120 | 121 | 122 | 123 | 124 | ## [1.4.1-alpha.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.4.0...basic-starter@1.4.1-alpha.0) (2022-04-18) 125 | 126 | 127 | ### Bug Fixes 128 | 129 | * update tests ([0f4d49e](https://github.com/chapter-three/next-drupal/commit/0f4d49e9bb3b8767577bdba4ef52d7e58ad6bf91)) 130 | 131 | 132 | 133 | 134 | 135 | # [1.4.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.3.1...basic-starter@1.4.0) (2022-04-11) 136 | 137 | 138 | ### Bug Fixes 139 | 140 | * **basic-starter:** use a tag for exit preview ([8f68023](https://github.com/chapter-three/next-drupal/commit/8f680232a53740f083fd7c208b54f4293ad0f58a)) 141 | 142 | 143 | ### Features 144 | 145 | * **basic-starter:** show only published articles on index ([d209af9](https://github.com/chapter-three/next-drupal/commit/d209af9c08b4db12f8c2f7adfb7adfc3840a8f02)) 146 | * **basic-starter:** update starter ([ad6afa9](https://github.com/chapter-three/next-drupal/commit/ad6afa999b59f49d5f6b199aaa4b3e3c1683c352)) 147 | 148 | 149 | 150 | 151 | 152 | ## [1.3.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.3.0...basic-starter@1.3.1) (2022-03-28) 153 | 154 | **Note:** Version bump only for package basic-starter 155 | 156 | 157 | 158 | 159 | 160 | # [1.3.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.2.0...basic-starter@1.3.0) (2022-02-24) 161 | 162 | 163 | ### Bug Fixes 164 | 165 | * **basic-starter:** fix type for private in package.json ([b1a6e90](https://github.com/chapter-three/next-drupal/commit/b1a6e907e22de61354b42b0126e5a084bd90f57b)) 166 | * **basic-starter:** update @tailwindcss/typography ([aa70f4e](https://github.com/chapter-three/next-drupal/commit/aa70f4ee6287e7fcc6ce1352114a4ef24474e404)) 167 | * **basic-starter:** update typescript ([3eda875](https://github.com/chapter-three/next-drupal/commit/3eda8755dbf2c904e9253bff7df67a9992bbdc12)) 168 | 169 | 170 | ### Features 171 | 172 | * bump all examples to next 12.1.0 ([00b15f2](https://github.com/chapter-three/next-drupal/commit/00b15f2b308a0a9fcb298789a9ca712f4efa7eff)) 173 | * **basic-starter:** simplify starter by removing menus ([7ce44ac](https://github.com/chapter-three/next-drupal/commit/7ce44ac11b628f06849b09a1831069df5da2a926)) 174 | * **basic-starter:** use server-side menus ([58f1150](https://github.com/chapter-three/next-drupal/commit/58f1150e750d860cb62b60f28edca3673dbb3c68)) 175 | 176 | 177 | 178 | 179 | 180 | # [1.2.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.1.1...basic-starter@1.2.0) (2022-01-17) 181 | 182 | 183 | ### Features 184 | 185 | * **basic-starter:** update tailwind and dependencies ([5de7337](https://github.com/chapter-three/next-drupal/commit/5de7337c7372afe44692b3ba49bcf10afdf9cfd6)) 186 | 187 | 188 | 189 | 190 | 191 | ## [1.1.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.1.0...basic-starter@1.1.1) (2022-01-12) 192 | 193 | **Note:** Version bump only for package basic-starter 194 | 195 | 196 | 197 | 198 | 199 | # [1.1.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@1.0.0...basic-starter@1.1.0) (2021-12-21) 200 | 201 | 202 | ### Features 203 | 204 | * **basic-starter:** bump next to 12 ([3185cd4](https://github.com/chapter-three/next-drupal/commit/3185cd4f720e87e91d2a03e335729a6ae8df4e78)) 205 | 206 | 207 | 208 | 209 | 210 | # [1.0.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.7.0...basic-starter@1.0.0) (2021-12-03) 211 | 212 | **Note:** Version bump only for package basic-starter 213 | 214 | 215 | 216 | 217 | 218 | # [0.7.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.6.0...basic-starter@0.7.0) (2021-11-24) 219 | 220 | 221 | ### Features 222 | 223 | * **basic-starter:** update dependencies and components ([233549b](https://github.com/chapter-three/next-drupal/commit/233549b1c2c3f401fac9b4290dcbe53682670d2f)) 224 | 225 | 226 | 227 | 228 | 229 | # [0.6.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.6.0-alpha.0...basic-starter@0.6.0) (2021-11-01) 230 | 231 | **Note:** Version bump only for package basic-starter 232 | 233 | 234 | 235 | 236 | 237 | # [0.6.0-alpha.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.5.2...basic-starter@0.6.0-alpha.0) (2021-11-01) 238 | 239 | **Note:** Version bump only for package basic-starter 240 | 241 | 242 | 243 | 244 | 245 | ## [0.5.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.5.1...basic-starter@0.5.2) (2021-10-14) 246 | 247 | **Note:** Version bump only for package basic-starter 248 | 249 | 250 | 251 | 252 | 253 | ## [0.5.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.5.0...basic-starter@0.5.1) (2021-10-14) 254 | 255 | **Note:** Version bump only for package basic-starter 256 | 257 | 258 | 259 | 260 | 261 | # [0.5.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.4.2...basic-starter@0.5.0) (2021-10-13) 262 | 263 | 264 | ### Bug Fixes 265 | 266 | * rename repo links ([48d52dd](https://github.com/chapter-three/next-drupal/commit/48d52dde79f69396ef706d152c03670117b6a480)) 267 | * **basic-starter:** update next ([ea46504](https://github.com/chapter-three/next-drupal/commit/ea465044bec1865bab850f588e856be2fcaaf34c)) 268 | 269 | 270 | ### Features 271 | 272 | * **basic-starter:** update the basic starter ([a7efdcd](https://github.com/chapter-three/next-drupal/commit/a7efdcdf2fb38057027aad12e11e63ba21318b32)) 273 | 274 | 275 | 276 | 277 | 278 | ## [0.4.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.4.1...basic-starter@0.4.2) (2021-08-11) 279 | 280 | **Note:** Version bump only for package basic-starter 281 | 282 | 283 | 284 | 285 | 286 | ## [0.4.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.4.0...basic-starter@0.4.1) (2021-08-07) 287 | 288 | **Note:** Version bump only for package basic-starter 289 | 290 | 291 | 292 | 293 | 294 | # [0.4.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.9...basic-starter@0.4.0) (2021-06-22) 295 | 296 | 297 | ### Bug Fixes 298 | 299 | * **basic-starter:** add props to NodeMeta ([613a2e1](https://github.com/chapter-three/next-drupal/commit/613a2e1c732b2fe94538ffdd66e42d3af60d0088)) 300 | 301 | 302 | ### Features 303 | 304 | * **basic-starter:** update the basic starter ([db2f99c](https://github.com/chapter-three/next-drupal/commit/db2f99c3872a7e46cedcad66650b6f03fd645dbb)) 305 | 306 | 307 | 308 | 309 | 310 | ## [0.3.9](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.8...basic-starter@0.3.9) (2021-06-16) 311 | 312 | **Note:** Version bump only for package basic-starter 313 | 314 | 315 | 316 | 317 | 318 | ## [0.3.8](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.7...basic-starter@0.3.8) (2021-06-16) 319 | 320 | **Note:** Version bump only for package basic-starter 321 | 322 | 323 | 324 | 325 | 326 | ## [0.3.7](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.6...basic-starter@0.3.7) (2021-06-15) 327 | 328 | **Note:** Version bump only for package basic-starter 329 | 330 | 331 | 332 | 333 | 334 | ## [0.3.6](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.5...basic-starter@0.3.6) (2021-06-14) 335 | 336 | **Note:** Version bump only for package basic-starter 337 | 338 | 339 | 340 | 341 | 342 | ## [0.3.5](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.4...basic-starter@0.3.5) (2021-06-13) 343 | 344 | **Note:** Version bump only for package basic-starter 345 | 346 | 347 | 348 | 349 | 350 | ## [0.3.4](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.3...basic-starter@0.3.4) (2021-06-13) 351 | 352 | **Note:** Version bump only for package basic-starter 353 | 354 | 355 | 356 | 357 | 358 | ## [0.3.3](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.2...basic-starter@0.3.3) (2021-06-13) 359 | 360 | **Note:** Version bump only for package basic-starter 361 | 362 | 363 | 364 | 365 | 366 | ## [0.3.2](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.1...basic-starter@0.3.2) (2021-06-11) 367 | 368 | **Note:** Version bump only for package basic-starter 369 | 370 | 371 | 372 | 373 | 374 | ## [0.3.1](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.3.0...basic-starter@0.3.1) (2021-06-10) 375 | 376 | **Note:** Version bump only for package basic-starter 377 | 378 | 379 | 380 | 381 | 382 | # [0.3.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.2.0...basic-starter@0.3.0) (2021-05-17) 383 | 384 | 385 | ### Features 386 | 387 | * add getEntityByPath ([072ead7](https://github.com/chapter-three/next-drupal/commit/072ead7ecc3b7f158e4b81e03d17f0bf1a5b511c)) 388 | 389 | 390 | 391 | 392 | 393 | # [0.2.0](https://github.com/chapter-three/next-drupal/compare/basic-starter@0.1.0...basic-starter@0.2.0) (2021-05-17) 394 | 395 | 396 | ### Features 397 | 398 | * deserialize entities by default ([8b53ae2](https://github.com/chapter-three/next-drupal/commit/8b53ae222717b8983568194373be04903944a032)) 399 | 400 | 401 | 402 | 403 | 404 | # 0.1.0 (2021-05-07) 405 | 406 | 407 | ### Features 408 | 409 | * add basic-starter ([92b746a](https://github.com/chapter-three/next-drupal/commit/92b746aef6b59d893cb3c2f49d35d7dcc733c7c8)) 410 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basic Starter 2 | 3 | A simple starter for building your site with Next.js and Drupal. 4 | 5 | ## How to use 6 | 7 | `npx create-next-app -e https://github.com/chapter-three/next-drupal-basic-starter` 8 | 9 | ## Deploy to Vercel 10 | 11 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fchapter-three%2Fnext-drupal-basic-starter&env=NEXT_PUBLIC_DRUPAL_BASE_URL,NEXT_IMAGE_DOMAIN,DRUPAL_CLIENT_ID,DRUPAL_CLIENT_SECRET&envDescription=Learn%20more%20about%20environment%20variables&envLink=https%3A%2F%2Fnext-drupal.org%2Fdocs%2Fenvironment-variables&project-name=next-drupal&demo-title=Next.js%20for%20Drupal&demo-description=A%20next-generation%20front-end%20for%20your%20Drupal%20site.&demo-url=https%3A%2F%2Fdemo.next-drupal.org&demo-image=https%3A%2F%2Fnext-drupal.org%2Fimages%2Fdemo-screenshot.jpg) 12 | 13 | ## Documentation 14 | 15 | See https://next-drupal.org 16 | -------------------------------------------------------------------------------- /app/[...slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { draftMode } from "next/headers" 2 | import { notFound } from "next/navigation" 3 | import { getDraftData } from "next-drupal/draft" 4 | import { Article } from "@/components/drupal/Article" 5 | import { BasicPage } from "@/components/drupal/BasicPage" 6 | import { drupal } from "@/lib/drupal" 7 | import type { Metadata, ResolvingMetadata } from "next" 8 | import type { DrupalNode, JsonApiParams } from "next-drupal" 9 | 10 | async function getNode(slug: string[]) { 11 | const path = `/${slug.join("/")}` 12 | 13 | const params: JsonApiParams = {} 14 | 15 | const draftData = await getDraftData() 16 | 17 | if (draftData.path === path) { 18 | params.resourceVersion = draftData.resourceVersion 19 | } 20 | 21 | // Translating the path also allows us to discover the entity type. 22 | const translatedPath = await drupal.translatePath(path) 23 | 24 | if (!translatedPath) { 25 | throw new Error("Resource not found", { cause: "NotFound" }) 26 | } 27 | 28 | const type = translatedPath.jsonapi?.resourceName! 29 | const uuid = translatedPath.entity.uuid 30 | const tag = `${translatedPath.entity.type}:${translatedPath.entity.id}` 31 | 32 | if (type === "node--article") { 33 | params.include = "field_image,uid" 34 | } 35 | 36 | const resource = await drupal.getResource(type, uuid, { 37 | params, 38 | cache: "force-cache", 39 | next: { 40 | revalidate: 3600, 41 | // Replace `revalidate` with `tags` if using tag based revalidation. 42 | // tags: [tag], 43 | }, 44 | }) 45 | 46 | if (!resource) { 47 | throw new Error( 48 | `Failed to fetch resource: ${translatedPath?.jsonapi?.individual}`, 49 | { 50 | cause: "DrupalError", 51 | } 52 | ) 53 | } 54 | 55 | return resource 56 | } 57 | 58 | type NodePageParams = { 59 | slug: string[] 60 | } 61 | type NodePageProps = { 62 | params: Promise 63 | searchParams: Promise<{ [key: string]: string | string[] | undefined }> 64 | } 65 | 66 | export async function generateMetadata( 67 | props: NodePageProps, 68 | parent: ResolvingMetadata 69 | ): Promise { 70 | const params = await props.params 71 | 72 | const { slug } = params 73 | 74 | let node 75 | try { 76 | node = await getNode(slug) 77 | } catch (e) { 78 | // If we fail to fetch the node, don't return any metadata. 79 | return {} 80 | } 81 | 82 | return { 83 | title: node.title, 84 | } 85 | } 86 | 87 | const RESOURCE_TYPES = ["node--page", "node--article"] 88 | 89 | export async function generateStaticParams(): Promise { 90 | const resources = await drupal.getResourceCollectionPathSegments( 91 | RESOURCE_TYPES, 92 | { 93 | // The pathPrefix will be removed from the returned path segments array. 94 | // pathPrefix: "/blog", 95 | // The list of locales to return. 96 | // locales: ["en", "es"], 97 | // The default locale. 98 | // defaultLocale: "en", 99 | } 100 | ) 101 | 102 | return resources.map((resource) => { 103 | // resources is an array containing objects like: { 104 | // path: "/blog/some-category/a-blog-post", 105 | // type: "node--article", 106 | // locale: "en", // or `undefined` if no `locales` requested. 107 | // segments: ["blog", "some-category", "a-blog-post"], 108 | // } 109 | return { 110 | slug: resource.segments, 111 | } 112 | }) 113 | } 114 | 115 | export default async function NodePage(props: NodePageProps) { 116 | const params = await props.params 117 | 118 | const { slug } = params 119 | 120 | const draft = await draftMode() 121 | const isDraftMode = draft.isEnabled 122 | 123 | let node 124 | try { 125 | node = await getNode(slug) 126 | } catch (error) { 127 | // If getNode throws an error, tell Next.js the path is 404. 128 | notFound() 129 | } 130 | 131 | // If we're not in draft mode and the resource is not published, return a 404. 132 | if (!isDraftMode && node?.status === false) { 133 | notFound() 134 | } 135 | 136 | return ( 137 | <> 138 | {node.type === "node--page" && } 139 | {node.type === "node--article" &&
} 140 | 141 | ) 142 | } 143 | -------------------------------------------------------------------------------- /app/api/disable-draft/route.ts: -------------------------------------------------------------------------------- 1 | import { disableDraftMode } from "next-drupal/draft" 2 | import type { NextRequest } from "next/server" 3 | 4 | export async function GET(_: NextRequest) { 5 | return await disableDraftMode() 6 | } 7 | -------------------------------------------------------------------------------- /app/api/draft/route.ts: -------------------------------------------------------------------------------- 1 | import { drupal } from "@/lib/drupal" 2 | import { enableDraftMode } from "next-drupal/draft" 3 | import type { NextRequest } from "next/server" 4 | 5 | export async function GET(request: NextRequest): Promise { 6 | return await enableDraftMode(request, drupal) 7 | } 8 | -------------------------------------------------------------------------------- /app/api/revalidate/route.ts: -------------------------------------------------------------------------------- 1 | import { revalidatePath, revalidateTag } from "next/cache" 2 | import type { NextRequest } from "next/server" 3 | 4 | async function handler(request: NextRequest) { 5 | const searchParams = request.nextUrl.searchParams 6 | const path = searchParams.get("path") 7 | const tags = searchParams.get("tags") 8 | const secret = searchParams.get("secret") 9 | 10 | // Validate secret. 11 | if (secret !== process.env.DRUPAL_REVALIDATE_SECRET) { 12 | return new Response("Invalid secret.", { status: 401 }) 13 | } 14 | 15 | // Either tags or path must be provided. 16 | if (!path && !tags) { 17 | return new Response("Missing path or tags.", { status: 400 }) 18 | } 19 | 20 | try { 21 | path && revalidatePath(path) 22 | tags?.split(",").forEach((tag) => revalidateTag(tag)) 23 | 24 | return new Response("Revalidated.") 25 | } catch (error) { 26 | return new Response((error as Error).message, { status: 500 }) 27 | } 28 | } 29 | 30 | export { handler as GET, handler as POST } 31 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { DraftAlert } from "@/components/misc/DraftAlert" 2 | import { HeaderNav } from "@/components/navigation/HeaderNav" 3 | import type { Metadata } from "next" 4 | import type { ReactNode } from "react" 5 | 6 | import "@/styles/globals.css" 7 | 8 | export const metadata: Metadata = { 9 | title: { 10 | default: "Next.js for Drupal", 11 | template: "%s | Next.js for Drupal", 12 | }, 13 | description: "A Next.js site powered by a Drupal backend.", 14 | icons: { 15 | icon: "/favicon.ico", 16 | }, 17 | } 18 | 19 | export default function RootLayout({ 20 | // Layouts must accept a children prop. 21 | // This will be populated with nested layouts or pages 22 | children, 23 | }: { 24 | children: ReactNode 25 | }) { 26 | return ( 27 | 28 | 29 | 30 |
31 | 32 |
{children}
33 |
34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { ArticleTeaser } from "@/components/drupal/ArticleTeaser" 2 | import { drupal } from "@/lib/drupal" 3 | import type { Metadata } from "next" 4 | import type { DrupalNode } from "next-drupal" 5 | 6 | export const metadata: Metadata = { 7 | description: "A Next.js site powered by a Drupal backend.", 8 | } 9 | 10 | export default async function Home() { 11 | const nodes = await drupal.getResourceCollection( 12 | "node--article", 13 | { 14 | params: { 15 | "filter[status]": 1, 16 | "fields[node--article]": "title,path,field_image,uid,created", 17 | include: "field_image,uid", 18 | sort: "-created", 19 | }, 20 | next: { 21 | revalidate: 3600, 22 | }, 23 | } 24 | ) 25 | 26 | return ( 27 | <> 28 |

Latest Articles.

29 | {nodes?.length ? ( 30 | nodes.map((node) => ( 31 |
32 | 33 |
34 |
35 | )) 36 | ) : ( 37 |

No nodes found

38 | )} 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /components/drupal/Article.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | import { absoluteUrl, formatDate } from "@/lib/utils" 3 | import type { DrupalNode } from "next-drupal" 4 | 5 | interface ArticleProps { 6 | node: DrupalNode 7 | } 8 | 9 | export function Article({ node, ...props }: ArticleProps) { 10 | return ( 11 |
12 |

{node.title}

13 |
14 | {node.uid?.display_name ? ( 15 | 16 | Posted by{" "} 17 | {node.uid?.display_name} 18 | 19 | ) : null} 20 | - {formatDate(node.created)} 21 |
22 | {node.field_image && ( 23 |
24 | {node.field_image.resourceIdObjMeta.alt 31 | {node.field_image.resourceIdObjMeta.title && ( 32 |
33 | {node.field_image.resourceIdObjMeta.title} 34 |
35 | )} 36 |
37 | )} 38 | {node.body?.processed && ( 39 |
43 | )} 44 |
45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /components/drupal/ArticleTeaser.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image" 2 | import { Link } from "@/components/navigation/Link" 3 | import { absoluteUrl, formatDate } from "@/lib/utils" 4 | import type { DrupalNode } from "next-drupal" 5 | 6 | interface ArticleTeaserProps { 7 | node: DrupalNode 8 | } 9 | 10 | export function ArticleTeaser({ node, ...props }: ArticleTeaserProps) { 11 | return ( 12 |
13 | 14 |

{node.title}

15 | 16 |
17 | {node.uid?.display_name ? ( 18 | 19 | Posted by{" "} 20 | {node.uid?.display_name} 21 | 22 | ) : null} 23 | - {formatDate(node.created)} 24 |
25 | {node.field_image && ( 26 |
27 | {node.field_image.resourceIdObjMeta.alt} 33 |
34 | )} 35 | 39 | Read article 40 | 49 | 50 | 51 | 52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /components/drupal/BasicPage.tsx: -------------------------------------------------------------------------------- 1 | import type { DrupalNode } from "next-drupal" 2 | 3 | interface BasicPageProps { 4 | node: DrupalNode 5 | } 6 | 7 | export function BasicPage({ node, ...props }: BasicPageProps) { 8 | return ( 9 |
10 |

{node.title}

11 | {node.body?.processed && ( 12 |
16 | )} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /components/misc/DraftAlert/Client.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useEffect, useState } from "react" 4 | 5 | export function DraftAlertClient({ 6 | isDraftEnabled, 7 | }: { 8 | isDraftEnabled: boolean 9 | }) { 10 | const [showDraftAlert, setShowDraftAlert] = useState(false) 11 | 12 | useEffect(() => { 13 | setShowDraftAlert(isDraftEnabled && window.top === window.self) 14 | }, [isDraftEnabled]) 15 | 16 | if (!showDraftAlert) { 17 | return null 18 | } 19 | 20 | function buttonHandler() { 21 | void fetch("/api/disable-draft") 22 | setShowDraftAlert(false) 23 | } 24 | 25 | return ( 26 |
27 |

28 | This page is a draft. 29 | 35 |

36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /components/misc/DraftAlert/index.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react" 2 | import { draftMode } from "next/headers" 3 | import { DraftAlertClient } from "./Client" 4 | 5 | export async function DraftAlert() { 6 | const draft = await draftMode() 7 | const isDraftEnabled = draft.isEnabled 8 | 9 | return ( 10 | 11 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /components/navigation/HeaderNav.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@/components/navigation/Link" 2 | 3 | export function HeaderNav() { 4 | return ( 5 |
6 |
7 | 8 | Next.js for Drupal 9 | 10 | 16 | Read the docs 17 | 18 |
19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /components/navigation/Link.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from "react" 2 | import NextLink from "next/link" 3 | import type { AnchorHTMLAttributes, ReactNode } from "react" 4 | import type { LinkProps as NextLinkProps } from "next/link" 5 | 6 | type LinkProps = NextLinkProps & 7 | Omit, keyof NextLinkProps> & { 8 | children?: ReactNode 9 | } 10 | 11 | export const Link = forwardRef( 12 | function LinkWithRef( 13 | { 14 | // Turn next/link prefetching off by default. 15 | // @see https://github.com/vercel/next.js/discussions/24009 16 | prefetch = false, 17 | ...rest 18 | }, 19 | ref 20 | ) { 21 | return 22 | } 23 | ) 24 | -------------------------------------------------------------------------------- /lib/drupal.ts: -------------------------------------------------------------------------------- 1 | import { NextDrupal } from "next-drupal" 2 | 3 | const baseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL as string 4 | const clientId = process.env.DRUPAL_CLIENT_ID as string 5 | const clientSecret = process.env.DRUPAL_CLIENT_SECRET as string 6 | 7 | export const drupal = new NextDrupal(baseUrl, { 8 | // Enable to use authentication 9 | // auth: { 10 | // clientId, 11 | // clientSecret, 12 | // }, 13 | // withAuth: true, 14 | // debug: true, 15 | }) 16 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | export function formatDate(input: string): string { 2 | const date = new Date(input) 3 | return date.toLocaleDateString("en-US", { 4 | month: "long", 5 | day: "numeric", 6 | year: "numeric", 7 | }) 8 | } 9 | 10 | export function absoluteUrl(input: string) { 11 | return `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}${input}` 12 | } 13 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | images: { 5 | remotePatterns: [ 6 | { 7 | // protocol: 'https', 8 | hostname: process.env.NEXT_IMAGE_DOMAIN, 9 | // port: '', 10 | // pathname: '/sites/default/files/**', 11 | }, 12 | ], 13 | }, 14 | } 15 | 16 | module.exports = nextConfig 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basic-starter", 3 | "version": "2.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "next dev --turbopack", 8 | "build": "next build", 9 | "start": "next start", 10 | "preview": "next build && next start", 11 | "lint": "next lint", 12 | "format": "prettier --write .", 13 | "format:check": "prettier --check ." 14 | }, 15 | "dependencies": { 16 | "next": "^15.1.2", 17 | "react": "^19.0.0", 18 | "react-dom": "^19.0.0", 19 | "next-drupal": "^2.0.0-beta.2" 20 | }, 21 | "devDependencies": { 22 | "@tailwindcss/typography": "^0.5.12", 23 | "@types/node": "^20.12.7", 24 | "@types/react": "^19.0.0", 25 | "@types/react-dom": "^19.0.0", 26 | "autoprefixer": "^10.4.19", 27 | "eslint": "^8.57.0", 28 | "eslint-config-next": "^15.0.4", 29 | "postcss": "^8.4.38", 30 | "prettier": "^3.2.5", 31 | "tailwindcss": "^3.4.3", 32 | "typescript": "^5.4.5" 33 | }, 34 | "overrides": { 35 | "@types/react": "^19.0.0", 36 | "@types/react-dom": "^19.0.0" 37 | } 38 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chapter-three/next-drupal-basic-starter/9de610b49e3ddcb43b4a7e87eeb6476405200658/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss" 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: {}, 11 | }, 12 | variants: { 13 | extend: {}, 14 | }, 15 | plugins: [require("@tailwindcss/typography")], 16 | } 17 | 18 | export default config 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | --------------------------------------------------------------------------------