👨‍💻

非エンジニアが生成AIを全力で活用して独力でサイト構築をやってみたけど余裕でしんどかった話

2024/10/30に公開

ソフトウェア開発生成AI?

ChatGPTを皮切りに生成AIが私たちの生活に浸透してから久しいですが、最近はソフトウェア開発を支援するための生成AIが話題に事欠かない状況になっています。
この記事の投稿しようとした日(2024-10-30)も、以下のようなGithub CEOのtweetが流れ、また大きな波が起ころうとしています。
https://x.com/ashtom/status/1851333075374051725

巷では「5分で作りたいWEBsiteやapplicationが作れる!」みたいな話が多いですが、非エンジニアである私がこれらの生成AIを全力で活用して作りたかったものを作ってみた記録を残しておこうと思います。⇒作ったサイト

はじめに

はじめまして。今日からブログを書いてみようと思っています。

タイトルで非エンジニアと述べていますが、技術に対して完全に知見がないわけではないです。
はじめに少し自己紹介をしておきます。

もともと私は総合系のコンサルでIT寄りの業務やってました。
Business ApplicationとしてのSalesforceの専門性でProjectに入ることがメインでしたが、
その他にもCDP(Customer Data Platform)の文脈でData Analytics/Visualization(TableauやPower BI)などデータ利活用のProjectにも関わっていました。
楽しく仕事はしていたのですが、ずーと心にあった海外挑戦したかったこともあり、
職を辞して2024年7月からオーストラリアに留学中です。
(ただいま語学学校が終わり時間があるので、これまでやりたいと思っていた開発を進めています!)

一方で、後述するようなサイト構築といったようなWEB系の開発は全く経験がなく
HTML,CSS,JSやfront-end/back-endの仕組みなどは、「まぁ何となく、際の際の表面上はわかる」ぐらいの感じでした。
コンサル時代にSalesforceプロジェクトではvscodeは割と必須なのである程度使えていたのと
あるプロジェクトではやり取りがGithub上だったことなどもあり、このあたりの使い方は多少はわかっています。(エンジニアとして入っていたわけではないので、細かい仕組みはわかっていないですが)

応用情報技術者も持っているので、非エンジニアという表現が正しいかどうかは皆様の判断に任せますが、上記のレベル感から開発を実践してみて割と大変だった経験をShareしたいと思っています。

このZennのブログは『桜井政博のゲーム作るには』のYouTubeチャネルを見て、アウトプットができる人の凄さを実感したことに起因しています。
https://youtube.com/playlist?list=PLi8SA3sbzYVS2vsvk5WUVVPJZP767eO72&si=vkSUfF_cNYmgIfL9
可能な限り今後も書いていこうと思うので、以後よろしくお願いいたします。

やろうとしたこと

とりあえず作りたいもののイメージはありました。
オーストラリア留学中の体験を伝えられるようにブログサイトです。(Zennのブログとの棲み分けて留学中のライフハックを書きたいと思っています。)

この時点からChatGPTとは会話しながらだったのですが、
一旦は私が投稿した記事を表示できるブログサイトを目指すことにしました。

生成AIを活用した開発

私がオーストラリアに来てからというもの、ソフトウェア開発生成AIが非常に大きなインパクトをもたらしていることをXを通して見聞きしていました。
以下のような感じのtweetが結構流れてきました。
https://x.com/masahirochaen/status/1842743779998232702

とりあえず当該サービスとしてよく見聞きするのが以下のサービスです。

無料でできるみたいので、とりあえず使って作ってみよう!といった気持ちで開発の扉を開きました。

1. create.xyz (2024/10/21~)

最初に手を付けたのcreate.xyzです。※理由は特にないです。
作りたいもの簡単に伝えほかのブログサイトなどを参考にしつつ多少のやり取りしただけで、
以下のような画面まで出来上がりました。

これはすごい!!
本当に思いました。

しかしここで一旦行き詰ります。
実際にブログ記事の運用まで想定していたので、記事を入稿するページを充実させたかったのですが、
いくらcreate.xyzに指示しても作られるページがエラーになって正しく返ってこなくなってしまいました。これ以降は、create.xyzとやり取りしても解決には向かわなかったです。

ただ、ここまで画面作ってくれてるし、これをベースにChatGPTとかと話ながら
このコードを修正していこうかな!なんてこの時は思っていました。

しゃーなし、コードを見てみます。

// 以下、かなり省略していますが、縦に長い1つのファイルが出力されていました。
"use client";
import React from "react";

function MainComponent() {
  const [posts, setPosts] = React.useState([]);
  const [isAdmin, setIsAdmin] = React.useState(false);
// --------------------------省略
  return (
    <div className="font-sans min-h-screen bg-gray-100">
      <header className="bg-blue-600 text-white p-4 flex justify-between items-center">
        <p className="text-xl">純ジャパアラサー、ワーホリ留学する</p>
      </header>
    </div>
  );
}
export default MainComponent;

はい、ここで気づきます。「え、何このコード?全然わからないけど。。。なんかHTMLとCSSとJSが一緒に記述されている?」
全く修正できる気がしないぞ。。。

2. ChatGPT (2024/10/21~)

とりあえずここまでわからないと頼るのは親友(ChatGPT)です。
私は生成AI系だとChatGPTのみ課金ユーザです。

まずこの書き方は何のか?
ChatGPTの回答は ReactをベースとしたおそらくNext.jsを利用したコードです。 といったことでした。

やばい、、、これはあまりにも知らなさすぎる。。。

ただもう作ると決めています。ゲームではないですが、桜井政博からもらったエネルギーをここで失ってはいけない。。。と奮い立たせてNext.jsを学びました。
ChatGPTに質問をしまくります。

  • Reactとは?
    • ライブラリとは?
  • Next.jsとは?
    • フレームワークとは?

なるほどなるほど、概要はちょっとわかった。
ただイメージがわかない。もう少し勉強しよう。

3. AI以外も当然活用 (2024/10/22~24)

ここでYoutubeでよい動画を見つけました。『しまぶーのIT大学』のNext.js講座です。
https://youtube.com/playlist?list=PLwM1-TnN_NN6fUhOoZyU4iZiwhLyISopO&si=DjqycZlS5zyBWzFW

一旦概要を把握しようと辛抱強くすべて見通しました。

  • モダンなWEB開発はコンポーネントをうまく使っているのね。 なるほどなるほど
  • WEBサイトとして展開するためにはデプロイまでしないといけないのね。 なるほどなるほど
  • VSCodeからGithubへの連携の方法はこんな感じでやればよいのね。 なるほどなるほど
  • ちょっとuse...とかは難しいけど、これは実装のタイミングで改めて見ればよいや

「よーし、これを参考にNext.jsのプロジェクトを立ち上げるぞ!」
と勢いで作り始めましたがなんか動画と全然違う、、、
まずディレクト構成が全然違う、、、
え、どういうこと?Pageのディレクトリは??

これは意味が分からん。またChatGPTに相談します。
Next.jsはversion13.4以降ファイルディレクトリ含めて構成が大きく変更になりました

はいそうです。上記の動画は非常にわかりやすいのですが、
動画の説明そのものがversion13.4より前のもので行われていて最新versionのものとはかなり異なる作りになっていたのです。

初心者はまず模倣するのが基本なのに、模倣できない。。。
しかし、初心者は最新版を使いたいのです。
この心理は自分でも謎ですが、最新版をかっこいいと思っているのです。
(これがまた後にに試練を引き起こすとも知らずに、、、)

また勉強するしかない。
前の動画ほど丁寧な説明をしている動画は見つけられなかったので(udemy等の有料教材は見ていません)Xで見つけた以下のドキュメントを頑張って読むことに
https://zenn.dev/akfm/books/nextjs-basic-principle/viewer/intro

これまた読むのはつらいけど、わからないところは親友(ChatGPT)に相談しながら読み進めました。

はいはい、Pages RouterからApp Router方式に変わったのね。
これが最新版に適応されているからディレクトリ構成違うのね。
話はなんかむずいけど、何となくclient側で処理するよりもserver側で処理するほうがいいよねって
感じよね?おっけー、よし、こっちの構成で作っていくか!って感じでこの勉強期間を乗り越えました。

4. v0 by vercel (2024/10/25~)

ここまでの5日間程度で表面上のNext.jsを少しわかってきた、という流れの中で
Next.jsの開発元であるvercelが先に紹介したソフトウェア開発生成AIサービスの一つであるv0 by vercelを提供していることを知りました。

「それならcreate.xyzよりv0使うほうがきっと良いよね。」と思いv0と対話を進めていくことにしました。

こちらも素晴らしい精度です。create.xyzより良いのは、単一ファイルで作られるのではなく
ディレクトリ構成の提案から各ファイルのコードを提供してくれるところですね。

以下、入出力です。細かいので興味のある人だけ見てください。

私の指示文

Next.js App Routerの基本的な仕組みを座学で勉強しました。これから実装を進めていきたいと思っています。ブログサイトを構築予定です。 まずは私だけが投稿するブログサイトを構築したいと思っています。これはネットに公開予定です。この作成においては、記事一覧と各記事のページ、自分だけが使う管理画面を作成予定です。管理画面で記事、タイトル、サムネイル画像、記事内の画像登録して、すべてデータベースに格納する予定です。そのデータベースから記事一覧と各記事ページを構成します。

v0の出力

Next.js App Routerを使用してブログサイトを構築するための基本的な実装方法を説明します。以下の手順に従って進めていきましょう。

  1. プロジェクトの基本構造

まず、Next.jsプロジェクトの基本構造を作成します。App Routerを使用する場合、以下のようなフォルダ構造になります:

/app
  /admin
    page.tsx
  /blog
    /[slug]
      page.tsx
    page.tsx
  layout.tsx
  page.tsx
/components
/lib
/public
  1. ルーティング

App Routerでは、フォルダ構造がそのままURLパスになります。

  • /: トップページ(app/page.tsx
  • /blog: 記事一覧ページ(app/blog/page.tsx
  • /blog/[slug]: 個別記事ページ(app/blog/[slug]/page.tsx
  • /admin: 管理画面(app/admin/page.tsx
  1. データベース接続とデータ取得

データベースとの接続には、例えばPrismaを使用できます。/lib/prisma.tsファイルを作成し、Prismaクライアントをエクスポートします:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

export default prisma
  1. 記事一覧ページの実装

app/blog/page.tsxに記事一覧ページを実装します:

import Link from 'next/link'
import prisma from '@/lib/prisma'

async function getBlogs() {
  const blogs = await prisma.blog.findMany({
    select: {
      id: true,
      title: true,
      slug: true,
      thumbnail: true,
    },
    orderBy: {
      createdAt: 'desc',
    },
  })
  return blogs
}

export default async function BlogList() {
  const blogs = await getBlogs()

  return (
    <div className="container mx-auto px-4">
      <h1 className="text-3xl font-bold mb-6">ブログ記事一覧</h1>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {blogs.map((blog) => (
          <Link href={`/blog/${blog.slug}`} key={blog.id} className="block">
            <div className="border rounded-lg overflow-hidden shadow-lg">
              <img src={blog.thumbnail} alt={blog.title} className="w-full h-48 object-cover" />
              <div className="p-4">
                <h2 className="text-xl font-semibold">{blog.title}</h2>
              </div>
            </div>
          </Link>
        ))}
      </div>
    </div>
  )
}
  1. 個別記事ページの実装

app/blog/[slug]/page.tsxに個別記事ページを実装します:

import prisma from '@/lib/prisma'

async function getBlogBySlug(slug: string) {
  const blog = await prisma.blog.findUnique({
    where: {
      slug: slug,
    },
  })
  return blog
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const blog = await getBlogBySlug(params.slug)

  if (!blog) {
    return <div>記事が見つかりません</div>
  }

  return (
    <article className="container mx-auto px-4 py-8">
      <h1 className="text-4xl font-bold mb-4">{blog.title}</h1>
      <img src={blog.thumbnail} alt={blog.title} className="w-full h-64 object-cover mb-6" />
      <div className="prose lg:prose-xl" dangerouslySetInnerHTML={{ __html: blog.content }} />
    </article>
  )
}
  1. 管理画面の基本実装

app/admin/page.tsxに管理画面の基本的な実装を行います:

'use client'

import { useState } from 'react'
import { useRouter } from 'next/navigation'

export default function AdminPage() {
  const [title, setTitle] = useState('')
  const [content, setContent] = useState('')
  const [thumbnail, setThumbnail] = useState<File | null>(null)
  const router = useRouter()

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    const formData = new FormData()
    formData.append('title', title)
    formData.append('content', content)
    if (thumbnail) {
      formData.append('thumbnail', thumbnail)
    }

    const response = await fetch('/api/blog', {
      method: 'POST',
      body: formData,
    })

    if (response.ok) {
      router.push('/blog')
    } else {
      console.error('投稿に失敗しました')
    }
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-6">新規記事投稿</h1>
      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label htmlFor="title" className="block text-sm font-medium text-gray-700">
            タイトル
          </label>
          <input
            type="text"
            id="title"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
            required
          />
        </div>
        <div>
          <label htmlFor="content" className="block text-sm font-medium text-gray-700">
            本文
          </label>
          <textarea
            id="content"
            value={content}
            onChange={(e) => setContent(e.target.value)}
            className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
            rows={10}
            required
          />
        </div>
        <div>
          <label htmlFor="thumbnail" className="block text-sm font-medium text-gray-700">
            サムネイル画像
          </label>
          <input
            type="file"
            id="thumbnail"
            onChange={(e) => setThumbnail(e.target.files?.[0] || null)}
            className="mt-1 block w-full"
            accept="image/*"
          />
        </div>
        <button
          type="submit"
          className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
        >
          投稿する
        </button>
      </form>
    </div>
  )
}
  1. API ルートの作成

app/api/blog/route.tsにAPI routeを作成し、記事の投稿処理を実装します:

import { NextResponse } from 'next/server'
import prisma from '@/lib/prisma'

export async function POST(request: Request) {
  const formData = await request.formData()
  const title = formData.get('title') as string
  const content = formData.get('content') as string
  const thumbnail = formData.get('thumbnail') as File

  // ここで画像のアップロード処理を行う(例:AWS S3やCloudinaryなどのサービスを使用)
  const thumbnailUrl = await uploadImage(thumbnail)

  const slug = title.toLowerCase().replace(/ /g, '-')

  const blog = await prisma.blog.create({
    data: {
      title,
      content,
      thumbnail: thumbnailUrl,
      slug,
    },
  })

  return NextResponse.json(blog)
}

// 画像アップロード関数(実際の実装はクラウドストレージサービスに依存)
async function uploadImage(file: File): Promise<string> {
  // ここに画像アップロードの実装を追加
  // 例: AWS S3, Cloudinary, またはその他のストレージサービスを使用
  return 'https://example.com/uploaded-image.jpg'
}

以上が、Next.js App Routerを使用したブログサイトの基本的な実装方法です。この実装には以下の特徴があります:

  • Server ComponentsとClient Componentsを適切に使い分けています。
  • データベースアクセスはサーバーサイドで行い、セキュリティを確保しています。
  • 管理画面は認証機能を追加する必要があります(この例では省略しています)。
  • 画像のアップロードと管理には、別途クラウドストレージサービスの使用を検討してください。

この基本的な実装をベースに、認証機能の追加、エラーハンドリングの改善、UIの洗練化などを行っていくことで、より完成度の高いブログサイトを構築できます。

本当にすごいですね。作りたいサービスの枠組みが一発である程度出てくる。
ここからもう少し詳細を詰めいていきます。(v0と対話をすすめる)

今回のトークテーマの本筋からそれてしまいますが、このやり取りの中でどこにホスティングさせるかなどもきちんと考えました。
『しまぶーのIT大学』が動画で紹介してたデプロイ先もvercelだし、とりあえずvercelが提供しているものベースにして今回のブログサイトを展開していくことを決めました。

また、(上記のトグル内の私の指示文であるような)記事内容をWEBサイト内から入稿→どこかのDBに保存→ユーザ側からのアクセスでそれを取得/表示、みたいな大変な処理ははやめて、
ヘッドレスCMS(microCMS)のサービスでブログ記事は管理して、API経由で取得して表示方針にしました。「まずは動くものを作る!」これはモチベーション管理的にも結構大事ですよね。

microCMSは直感的な使用感で入稿できますし、get startedでNext.js側に渡さないといけないAPI key周りの確認も容易でした。
もちろんv0にAPI連携の処理を依頼して、そちらの開発も問題なく進めることができました。

よし、これでとりあえず個人ブログサイトの枠組みはとりあえず作れただろう!!

それでも発生するエラー

親友(chatGPT)とも相談しながら一旦サイトの枠組みはある程度できただろう。という自信をもってデプロイします。
...
vercel→エラー!!!

エラーログは以下の通りです。

よくわからんけど、コンパイルしようとしたらなんかPropsの型がダメだと、、、
(※typescriptを使って型チェックしていることぐらい私もこのころにはわかっていました。)

v0や親友(chatGPT)に対応方法を確認です。
二人ともおおよそ同じ返答で以下の方針で修正コードを示してくれました。

  • TypeScriptがparamsを同期的なオブジェクトとして認識せず、Promiseの型(非同期処理)として期待している
    • paramsがPromise型でないことをTypeScriptに明確に伝える

Next.jsに詳しい方々は、サブタイトルの日付も含めてうすうす気づいている方もいるかもしれませんが、上記の修正方針ではこの問題は全く解決しませんでした。

私はこの問題の解決に丸々2日もかかりました。同じ質問を何度も繰り返すので、もはや親友(chatGPT)からも「とりあえずtypescriptの型チェックをfalseにしなよ」みたいな提案もされました。

「プロジェクト作成時にわざわざtypescript設定にして開発しているのに型チェックfalseにするのはダサいよなぁ、、、いやでもとりあえずはリリースしたほうがよいか。。。?」
困り果てた私は浮気相手(Claude 3.5 sonnet)に相談しました。
こちらも的を得た回答は得られなかったのですが、最後に以下の内容が記載されていました。

もし上記の修正を試してもエラーが解消されない場合は、以下の情報もお知らせいただけると助かります:

Next.jsのバージョン
TypeScriptのバージョン
発生している具体的なエラーメッセージ
package.json の依存関係

これらの情報があれば、より具体的な解決策をご提案できます。

はい、これです。この情報を見て、XやYoutubeかで誰かがめちゃくちゃ最近Next.jsが最新のversion15にバージョンアップしたという情報を発信していたのを思い出したのです!!
細かい話はよくわからなかったけど、破壊的変更がどうとか、キャッシュ戦略がどうとか、、

急いで自身のNext.jsのversionを確認して15であることを確認しました。

もしやこれ、情報が新しすぎて生成AI側の予測にはいっていないのでは、、、?

ということでNext.jsの公式ドキュメントでversion15の変更要素がまとめられたページ(Link)を親友(chatGPT)に読ませてあげて、それに基づいて対応方針を提示してもらいました。

原因さえわかれば賢い親友(chatGPT)は適切な回答を提供していくれます。非同期がマストなら非同期で処理するようにすればよい、という形でプログラムを修正してくれました。

v0は、Xでversion15の質問に答えられる!みたいなこと言ってたけど、このタイミングの通常のやり取りではversion15に対応した適切な回答はもらえなかったです。(開発元頑張ってほしい。。。)
https://x.com/v0/status/1848528420231057514

おわりに

というわけで、私の1週間ぐらいのブログサイト構築の取り組みを紹介しました。
まだブログのコンテンツを何も書いていないのですが、サイトの枠組みはこんな感じで出来ました。これからコンテンツを充実させて、オーストラリア留学を検討している方向けに価値のある記事を作っていきたいと思っています。

Zennブログ向けとしては、非エンジニアが生成AIを割と全力で活用してもサイトの枠組みだけでも作るのに割と時間がかかったよ、というお話でした。。。
今回紹介していないですが、別のWEBサイトも同時に作っていてそっちでBolt.newを使った感じこちらもファイル分割して適切な出力をしてくれていました。(これはまた記事にします。デフォで作ったらNext.jsではなくviteを利用した形で出力されました。はい、これも何も知らないところからのスタートです。)

上記のようなソフトウェア開発生成AIは、すでに経験のあるエンジニアの方の生産性を飛躍的に上げるだろうな思います。一方で、本当に何もわからない非エンジニアがやるにはまだ正直ハードルがあります。(技術が発展するばより容易になると思いますが、)特にビジネスマンが生成AIが出力したものに対して、自信をもって会社や世の中にリリースするためには、その良し悪しを判断するためのベースとなる知識が必要になるはずです。この点で専門性の高いエンジニアの需要な変わりようがない気がします。

一方、生成AIのおかげであり得ないほど学習コストが下がっています。周辺の学習ツールも活用しながら進めれば、動くものを確認しつつ、分からない場所を丁寧に教えてくれる親友を24時間そばにおいて学習できます。
時間を少し持て余している人はぜひ挑戦してみるとよいと思います!

最後に、「もっとこんな使い方すれば上手く作れるよ!」といったアドバイスなど経験者からも意見をもらえると嬉しいです。

以上です。読んでいただいてありがとうございます。

Discussion