・luxla・

Drupal × Remixで遊んでたら、なんかちゃんとしたサイトできちゃった件

drupal x remix
Posted on 2024/10/26

はじめに

お久しぶりです。
気づけば、まともにサイトを作るのは数年ぶり。
「もうそろそろ作ってもいい頃かな」と思いつつ、
億劫さに勝てずにNetflixばかり見ていたシハンセイカーエンジニアです。

で、最近ふと「Remix」ってやつを触ってみたら、
……これ、めちゃくちゃ良いじゃないか。
感触があまりにも良かったので、
せっかくだから暇つぶし全力モードでブログサイトを作ってみることにしました。

構成:とりあえず動くやつ

まずはdocker-compose.ymlを眺めながら、
「これくらいなら指が覚えてるだろ」と思って立ち上げた構成がこちら。

  • nginx
  • Drupal(on php-fpm)
  • Remix(Node)
  • MariaDB

……まぁ、よくある感じです。
最初はこれをそのままデプロイして、「お、俺やればできるじゃん」的な満足感を得ようとしたのですが、
現実はそんなに甘くありません。

メンテナンスコスト+サーバー代=趣味の域を軽く超える。
Nodeが動くレンタルサーバーを日本で探してみたけど、ない。
(あっても妙に怪しい。いや、怖い。)

そんな中、Next.jsのSSGが脳裏をよぎる。
「やっぱNextか…」と思いかけたそのとき、RemixにSPAモードなる存在が。

これだ!と思い、方針変更。
DrupalのAPIを叩いてJSONを保存し、
RemixをSPAモードで静的HTML化して、
それをさくらレンタルサーバーにrsyncするという、
地味に職人芸っぽい構成に着地しました。

Drupal:控えめに言って堅実

Drupal側は至ってシンプル。
構成の要は GraphQLgraphql_core_schema

graphql_core_schemaの何が良いかって、
Entity Queryが使えること。
もうこれだけで、「とりあえず一覧出したい」というニーズの8割をカバーします。

使っているのはノード・タクソノミー・イメージスタイル。
要するに、Drupalの「無駄に高機能だけど必要十分」なところをつまみ食いしてる感じです。

graphql

認証まわり:OAuthなんて知らん

使っているモジュールはこれだけ。

  • key_auth
  • metatag

SSGなのにOAuth?
……いやいや、そんな高尚なことしません。
「アノニマスはちょっと不安」くらいの理由で、
key_authで軽くロックをかける程度。
このへんの“適度な手抜き感”が、
年季の入ったエンジニアの知恵です(?)

GraphQL Codegen:型書かずに済む幸福

GraphQL Codegen、これは素晴らしい。
スキーマとクエリから型とコードを自動生成。
DrupalのGraphQLエンドポイントにさえアクセスできれば、
「型定義で時間を溶かす」というあの苦痛から解放されます。

設定はこんな感じ👇

 


import { CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: [
    {
      'http://nginx:8080/graphql': {
        headers: { 'api-key': process.env.DRUPAL_AUTH_KEY },
      },
    },
  ],
  documents: ['./graphql/documents/*.graphql'],
  generates: {
    './graphql/generated.ts': {
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-graphql-request",
      ]
    }
  }
}
 
export default config

これを走らせるだけで、
「TypeScriptでGraphQL叩くの怖くない世界」にワープできます。
50代でも快適。

Remix:静的なのに動的っぽく

ルート構成は以下のような感じ。

  • _index
  • article.$path

Drupal側ではPathautoを使って /article/ 配下にURLを整備。
SSRモードではローダーを定義して、
Remixから直接Drupalを叩くことも可能です。

 


export const loader = async ({ params }) => {
  const data = await Drupal.sdk().GetArticleByPath({path:'/article/' + params.path})

  if (!data.route) {
    throw new Response("Not Found", { status: 404 })
  }
  return { route: data.route }
}

が、今回はSPAモードなので、
静的JSONを読みに行く方針に。

 


export const clientLoader = async ({ params }) => {
  const index = await (await fetch("/data/index.json")).json()
  const item = index.entityQuery.items.find((i) => i.url.path == '/article/' + params.path)

  if (!item) throw new Response(null, { status: 404 })

  const data = await (await fetch("/data/"+ item.uuid +".json")).json()
  return { data }
}

はい、静的です。だけどなんか動いてるっぽい。
この“なんちゃって動的感”がRemixのいいところ。
(しかもレンタルサーバー代が安く済む!)

データ生成スクリプト:地味だけど要

この仕組みを支えているのが以下のスクリプト。
地味ですが、これが全ての要です。

 


import { writeFileSync } from "fs"
import { Drupal } from "lib/Drupal"

async function makeData() {
  const index = await Drupal.sdk().GetAllArticles()
  writeFileSync('./public/data/index.json', JSON.stringify(index, null, 2))

  for (const item of index.entityQuery.items) {
    const data = await Drupal.sdk().GetArticleByPath({ path: item.url.path })
    Drupal.transform(data)
    writeFileSync(`./public/data/${item.uuid}.json`, JSON.stringify(data, null, 2))
  }
}

makeData()

このスクリプトを叩くだけで、
DrupalのAPIレスポンスが静的JSONとして吐き出されます。
もうバックエンド要らない。俺の暇つぶし完了。

Drupalクラス:オトナの分別ある設計

最後に中核となるDrupalクラス。
コードの一部だけ抜粋します。

 


class _Drupal {
  host() {
    return process.env.DRUPAL_URL || 'http://nginx:8080'
  }

  client() {
    return new GraphQLClient(this.host() + '/graphql', {
      headers: () => ({ "api-key": process.env.DRUPAL_AUTH_KEY }),
    })
  }

  sdk() {
    return getSdk(this.client())
  }

  metatag(data) {
    return data.map((item) => {
      if (!item) return null
      if (item.tag === 'link') {
        item.attributes = { ...item.attributes, tagName: "link" }
      }
      return item.attributes
    })
  }
}

export const Drupal = new _Drupal()

大事なのは「無駄に凝らない」こと。
若い頃は“フルスタック”とか言って全方位に張ってたけど、
今は“最短ルートで目的達成”が信条です。

まとめ:暇つぶしは本気でやると危険

というわけで、Drupal × Remix の初回構築でした。
一見ふざけてるけど、ちゃんと動く。
(というか、思った以上にちゃんとしてしまった。)

このあと気が向いたら、

  • 目次機能の追加
  • 開発環境の改善
  • Google連携
    あたりをネタに、引き続き暇つぶししていこうと思います。

要するに、技術は遊びながら覚えるのが一番
大人の全力の暇つぶし、侮るなかれ。