CONTENT
ここから
1ページLPの流れをAIで自動生成する
この教材では、Google Apps ScriptのWebアプリで、LP構成メーカーを作ります。
LPとは、1つの商品・サービス・企画を紹介するための、1ページのWebページです。
難しく考えなくて大丈夫です。
LPは、基本的には次の流れでできています。
最初に興味を引く
↓
何の企画か伝える
↓
魅力を見せる
↓
画像や動画で雰囲気を伝える
↓
最後に行動してもらう
前回までに、企画書、画像プロンプト、15秒動画台本を作ってきました。
今回は、それらをまとめて、Webサイトの設計図にしていきます。
このページで完成するもの
今回作るのは、次のようなアプリです。
| 項目 | 内容 |
|---|---|
| 入力 | 企画タイトル、ターゲット、コンセプト、雰囲気 |
| AI生成 | LPの見出し、構成、CTA、掲載内容 |
| 表示 | Web画面に表示 |
| 保存 | スプレッドシートに履歴保存 |
今回のゴールはこれです。
1ページLPに何を載せるかを、
AIと一緒に整理できるようにする
LPをいきなりデザインしようとすると、手が止まります。
まずは、ページの流れを作ります。
1. LPの基本構成
LPは、次の5つに分けると作りやすくなります。
| 順番 | セクション | 役割 |
|---|---|---|
| 1 | ファーストビュー | 最初に見える大事な部分 |
| 2 | 企画の説明 | 何の企画か伝える |
| 3 | 魅力・特徴 | なぜ良いのか伝える |
| 4 | 画像・動画 | 雰囲気を見せる |
| 5 | 最後の案内 | 見た人に行動してもらう |
たとえば、学校祭案内サイトなら、こうなります。
ファーストビュー:
はじめてでも楽しめる学校祭ナビ
企画の説明:
見どころや回り方を分かりやすく紹介
魅力・特徴:
迷わない、楽しみ方が分かる、写真で雰囲気が伝わる
画像・動画:
校門、展示、屋台、ステージの写真
最後の案内:
気になる企画を見てみよう
このくらいで十分です。
LP構成は、完璧な文章ではなく、ページを作るための道しるべです。
2. 今回作るファイル
今回も、使うファイルは2つだけです。
Code.gs
index.html
| ファイル | 役割 |
|---|---|
| Code.gs | Gemini APIを呼び出し、LP構成を作る |
| index.html | 入力画面と結果表示を作る |
3. 事前準備
Googleスプレッドシートを作ります。
https://sheets.google.com/
ファイル名は次にします。
AI LP構成メーカー
スプレッドシートからApps Scriptを開きます。
拡張機能
↓
Apps Script
プロジェクト名も、次にします。
AI LP構成メーカー
4. APIキーを確認する
前回と同じ GEMINI_API_KEY を使います。
Apps Scriptの左側にある歯車アイコンを開きます。
プロジェクトの設定
↓
スクリプト プロパティ
次の名前で保存されていればOKです。
| プロパティ | 値 |
|---|---|
| GEMINI_API_KEY | 自分のGemini APIキー |
APIキーは人に見せないでください。
コードに直接書かず、スクリプトプロパティに保存します。
5. Code.gsを貼り付ける
Code.gs の中身をすべて消して、次のコードを貼り付けます。
const SHEET_NAME = 'LP構成履歴';
const MODEL_NAME = 'gemini-2.5-flash';
function doGet() {
setupSheet_();
return HtmlService
.createHtmlOutputFromFile('index')
.setTitle('AI LP構成メーカー')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function generateLpPlan(formData) {
validateFormData_(formData);
setupSheet_();
const prompt = buildLpPrompt_(formData);
const result = callGemini_(prompt);
saveHistory_(formData, result);
return {
ok: true,
text: result,
message: 'LP構成を作成しました。'
};
}
function buildLpPrompt_(formData) {
return `
あなたは、学生向けのWebサイト制作を手伝うやさしいWebディレクターです。
次の企画情報をもとに、1ページLPの構成を作ってください。
【企画タイトル】
${formData.title}
【ターゲット】
${formData.target}
【コンセプト】
${formData.concept}
【雰囲気】
${formData.tone}
【使いたい画像・動画】
${formData.visual || '未入力'}
【見た人にしてほしい行動】
${formData.action || '未入力'}
以下の形式で出してください。
1. LPタイトル
2. ファーストビュー
・メインコピー
・サブコピー
・ボタン文言
・使う画像のイメージ
3. 企画の説明
・見出し
・本文
4. 魅力・特徴
・特徴1
・特徴2
・特徴3
5. 画像・動画セクション
・載せる画像
・載せる動画
・見せ方
6. 最後の案内
・見出し
・本文
・ボタン文言
7. ページ全体の雰囲気
8. 発表で使える一言
条件:
・初心者でも作れるLP構成にする
・1ページで完結させる
・難しい言葉を使わない
・各文章は短めにする
・見出しは分かりやすくする
・画像や動画をどこに使うか分かるようにする
・最後に見る人が何をすればよいか明確にする
・学生がそのまま発表で使える形にする
・少し前向きで、作ってみたくなる雰囲気にする
`;
}
function callGemini_(prompt) {
const apiKey = PropertiesService
.getScriptProperties()
.getProperty('GEMINI_API_KEY');
if (!apiKey) {
throw new Error('GEMINI_API_KEY が設定されていません。');
}
const url =
'https://generativelanguage.googleapis.com/v1beta/models/' +
MODEL_NAME +
':generateContent?key=' +
encodeURIComponent(apiKey);
const payload = {
contents: [
{
role: 'user',
parts: [{ text: prompt }]
}
],
generationConfig: {
temperature: 0.7,
maxOutputTokens: 1400
}
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const statusCode = response.getResponseCode();
const responseText = response.getContentText();
if (statusCode !== 200) {
throw new Error('Gemini APIエラー:' + responseText);
}
const json = JSON.parse(responseText);
const text = json.candidates?.[0]?.content?.parts?.[0]?.text;
if (!text) {
throw new Error('AIの回答を取得できませんでした。');
}
return text;
}
function saveHistory_(formData, result) {
const sheet = SpreadsheetApp
.getActiveSpreadsheet()
.getSheetByName(SHEET_NAME);
sheet.appendRow([
new Date(),
formData.title,
formData.target,
formData.concept,
formData.tone,
formData.visual || '',
formData.action || '',
result
]);
}
function getRecentLpPlans() {
setupSheet_();
const sheet = SpreadsheetApp
.getActiveSpreadsheet()
.getSheetByName(SHEET_NAME);
const lastRow = sheet.getLastRow();
if (lastRow <= 1) {
return [];
}
const values = sheet.getRange(2, 1, lastRow - 1, 8).getValues();
return values
.map((row) => {
return {
createdAt: formatDate_(row[0]),
title: row[1],
target: row[2],
tone: row[4],
result: row[7]
};
})
.reverse()
.slice(0, 5);
}
function setupSheet_() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = ss.insertSheet(SHEET_NAME);
}
if (sheet.getLastRow() === 0) {
sheet.appendRow([
'作成日時',
'企画タイトル',
'ターゲット',
'コンセプト',
'雰囲気',
'画像・動画',
'行動',
'LP構成'
]);
}
}
function validateFormData_(formData) {
if (!formData) {
throw new Error('入力データがありません。');
}
const requiredFields = [
['title', '企画タイトル'],
['target', 'ターゲット'],
['concept', 'コンセプト'],
['tone', '雰囲気']
];
requiredFields.forEach(([key, label]) => {
if (!formData[key] || String(formData[key]).trim() === '') {
throw new Error(label + 'を入力してください。');
}
});
}
function formatDate_(date) {
if (!date) return '';
return Utilities.formatDate(
new Date(date),
Session.getScriptTimeZone(),
'yyyy/MM/dd HH:mm'
);
}
6. index.htmlを作る
Apps Scriptの左側にある「+」を押します。
+
↓
HTML
ファイル名は次にします。
index
index.html の中身をすべて消して、次のコードを貼り付けます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<base target="_top" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI LP構成メーカー</title>
<style>
:root {
--bg: #f8fafc;
--card: #ffffff;
--text: #111827;
--muted: #6b7280;
--line: #dbe3ee;
--primary: #0ea5e9;
--primary-dark: #0284c7;
--soft: #e0f2fe;
--success: #16a34a;
--danger: #dc2626;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: linear-gradient(180deg, #f0f9ff 0%, #eef2f8 100%);
color: var(--text);
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
line-height: 1.7;
}
.page {
width: min(1100px, calc(100% - 32px));
margin: 0 auto;
padding: 32px 0 48px;
}
.hero {
padding: 28px;
margin-bottom: 20px;
border: 1px solid var(--line);
border-radius: 24px;
background: var(--card);
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
}
.badge {
display: inline-flex;
padding: 6px 12px;
border-radius: 999px;
background: var(--soft);
color: var(--primary-dark);
font-size: 13px;
font-weight: 800;
}
h1 {
margin: 18px 0 8px;
font-size: clamp(28px, 5vw, 44px);
line-height: 1.15;
letter-spacing: -0.04em;
}
.lead {
margin: 0;
color: var(--muted);
}
.grid {
display: grid;
grid-template-columns: 0.85fr 1.15fr;
gap: 20px;
}
.card {
border: 1px solid var(--line);
border-radius: 24px;
background: var(--card);
box-shadow: 0 14px 32px rgba(15, 23, 42, 0.06);
overflow: hidden;
}
.card-header {
padding: 20px 22px;
border-bottom: 1px solid var(--line);
background: #fbfdff;
}
.card-header h2 {
margin: 0;
font-size: 20px;
}
.card-header p {
margin: 4px 0 0;
color: var(--muted);
font-size: 14px;
}
form {
padding: 22px;
}
label {
display: block;
margin-bottom: 14px;
font-weight: 800;
}
.hint {
display: block;
margin: 2px 0 6px;
color: var(--muted);
font-size: 13px;
font-weight: 500;
}
input,
textarea,
select {
width: 100%;
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px 14px;
background: #ffffff;
color: var(--text);
font: inherit;
outline: none;
}
textarea {
min-height: 92px;
resize: vertical;
}
input:focus,
textarea:focus,
select:focus {
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.13);
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 20px;
}
button {
border: 0;
border-radius: 14px;
padding: 12px 18px;
cursor: pointer;
font: inherit;
font-weight: 900;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.primary {
background: var(--primary);
color: #ffffff;
}
.primary:hover {
background: var(--primary-dark);
}
.secondary {
background: #eef2f7;
color: #111827;
}
.message {
margin: 0 22px 22px;
padding: 12px 14px;
border-radius: 14px;
display: none;
font-weight: 800;
}
.message.success {
display: block;
background: #ecfdf5;
color: var(--success);
}
.message.error {
display: block;
background: #fef2f2;
color: var(--danger);
}
.loading {
display: none;
padding: 14px;
margin: 0 22px 22px;
border-radius: 14px;
background: #fffbeb;
color: #92400e;
font-weight: 800;
}
.loading.show {
display: block;
}
.result {
padding: 22px;
}
.result-box {
min-height: 440px;
padding: 20px;
border-radius: 18px;
background: #111827;
color: #f9fafb;
white-space: pre-wrap;
overflow-wrap: anywhere;
font-size: 15px;
}
.result-empty {
color: #9ca3af;
}
.recent {
padding: 0 22px 22px;
}
.recent-item {
padding: 14px 0;
border-top: 1px solid var(--line);
}
.recent-item:first-child {
border-top: 0;
}
.recent-title {
margin: 0;
font-weight: 900;
}
.recent-meta {
margin: 2px 0 0;
color: var(--muted);
font-size: 13px;
}
.small-button {
margin-top: 8px;
padding: 8px 12px;
border-radius: 10px;
background: #eef2f7;
color: #111827;
font-size: 13px;
}
.empty {
color: var(--muted);
font-size: 14px;
}
@media (max-width: 920px) {
.grid {
grid-template-columns: 1fr;
}
.hero {
padding: 22px;
}
}
</style>
</head>
<body>
<main class="page">
<section class="hero">
<span class="badge">6-7 LP構成メーカー</span>
<h1>企画を<br />1ページにまとめる</h1>
<p class="lead">
伝えたいことを整理して、LPの流れをAIと一緒に作ります。
</p>
</section>
<section class="grid">
<div class="card">
<div class="card-header">
<h2>LPの材料を入力</h2>
<p>ここに入れた内容が、ページ構成のもとになります。</p>
</div>
<form id="lpForm">
<label>
企画タイトル
<span class="hint">例:放課後カフェマップ</span>
<input
id="title"
type="text"
placeholder="企画タイトル"
required
/>
</label>
<label>
ターゲット
<span class="hint">例:学校帰りに寄れる場所を探している学生</span>
<input
id="target"
type="text"
placeholder="誰に届けたいか"
required
/>
</label>
<label>
コンセプト
<span class="hint">例:放課後に使える居場所を分かりやすく紹介する</span>
<textarea
id="concept"
placeholder="この企画で伝えたい考え"
required
></textarea>
</label>
<label>
雰囲気
<span class="hint">ページ全体の印象を選びます</span>
<select id="tone" required>
<option value="">選択してください</option>
<option value="明るい・親しみやすい">明るい・親しみやすい</option>
<option value="おしゃれ・落ち着いた">おしゃれ・落ち着いた</option>
<option value="かわいい・やさしい">かわいい・やさしい</option>
<option value="かっこいい・テンポが良い">かっこいい・テンポが良い</option>
<option value="シンプル・分かりやすい">シンプル・分かりやすい</option>
</select>
</label>
<label>
使いたい画像・動画
<span class="hint">例:カフェの写真、15秒紹介動画、手元の画像</span>
<textarea
id="visual"
placeholder="LPに載せたい画像や動画"
></textarea>
</label>
<label>
見た人にしてほしい行動
<span class="hint">例:サイトを見る、参加する、申し込む</span>
<input
id="action"
type="text"
placeholder="最後にしてほしい行動"
/>
</label>
<div class="actions">
<button class="primary" type="submit" id="generateButton">
LP構成を作る
</button>
<button class="secondary" type="button" id="sampleButton">
サンプル入力
</button>
<button class="secondary" type="button" id="clearButton">
クリア
</button>
</div>
</form>
<div id="loadingBox" class="loading">
AIがLP構成を作っています。少し待ってください。
</div>
<p id="messageBox" class="message"></p>
</div>
<div class="card">
<div class="card-header">
<h2>生成されたLP構成</h2>
<p>この構成をもとに、Webページや発表資料へ進めます。</p>
</div>
<div class="result">
<div id="resultBox" class="result-box">
<span class="result-empty">ここにLP構成が表示されます。</span>
</div>
<div class="actions">
<button class="secondary" type="button" id="copyButton">
LP構成をコピー
</button>
</div>
</div>
<div class="card-header">
<h2>最近作ったLP構成</h2>
<p>最新5件を表示します。</p>
</div>
<div class="recent" id="recentLpPlans">
<p class="empty">まだ履歴がありません。</p>
</div>
</div>
</section>
</main>
<script>
const form = document.getElementById('lpForm');
const generateButton = document.getElementById('generateButton');
const sampleButton = document.getElementById('sampleButton');
const clearButton = document.getElementById('clearButton');
const copyButton = document.getElementById('copyButton');
const loadingBox = document.getElementById('loadingBox');
const messageBox = document.getElementById('messageBox');
const resultBox = document.getElementById('resultBox');
const fields = {
title: document.getElementById('title'),
target: document.getElementById('target'),
concept: document.getElementById('concept'),
tone: document.getElementById('tone'),
visual: document.getElementById('visual'),
action: document.getElementById('action')
};
form.addEventListener('submit', (event) => {
event.preventDefault();
const formData = getFormData();
setLoading(true);
showMessage('', '');
google.script.run
.withSuccessHandler((result) => {
setLoading(false);
resultBox.textContent = result.text;
showMessage(result.message || 'LP構成を作成しました。', 'success');
loadRecentLpPlans();
})
.withFailureHandler((error) => {
setLoading(false);
showMessage(error.message || '作成に失敗しました。', 'error');
})
.generateLpPlan(formData);
});
sampleButton.addEventListener('click', () => {
fields.title.value = '放課後カフェマップ';
fields.target.value = '学校帰りに友達と寄れる場所を探している学生';
fields.concept.value =
'勉強にも休憩にも使える、放課後の居場所を分かりやすく紹介する。';
fields.tone.value = '明るい・親しみやすい';
fields.visual.value =
'夕方のカフェの写真、スマホを見る手元、15秒の紹介動画。';
fields.action.value = 'サイトを見て、行ってみたいカフェを探す';
showMessage('サンプルを入力しました。自由に書き換えてください。', 'success');
});
clearButton.addEventListener('click', () => {
form.reset();
resultBox.innerHTML =
'<span class="result-empty">ここにLP構成が表示されます。</span>';
showMessage('', '');
});
copyButton.addEventListener('click', async () => {
const text = resultBox.textContent.trim();
if (!text || text === 'ここにLP構成が表示されます。') {
showMessage('コピーするLP構成がありません。', 'error');
return;
}
try {
await navigator.clipboard.writeText(text);
showMessage('LP構成をコピーしました。', 'success');
} catch (error) {
showMessage('コピーできませんでした。手動で選択してコピーしてください。', 'error');
}
});
function getFormData() {
return {
title: fields.title.value.trim(),
target: fields.target.value.trim(),
concept: fields.concept.value.trim(),
tone: fields.tone.value.trim(),
visual: fields.visual.value.trim(),
action: fields.action.value.trim()
};
}
function setLoading(isLoading) {
generateButton.disabled = isLoading;
generateButton.textContent = isLoading
? '作成中...'
: 'LP構成を作る';
loadingBox.classList.toggle('show', isLoading);
}
function showMessage(text, type) {
if (!text) {
messageBox.textContent = '';
messageBox.className = 'message';
return;
}
messageBox.textContent = text;
messageBox.className = 'message ' + type;
}
function loadRecentLpPlans() {
google.script.run
.withSuccessHandler(renderRecentLpPlans)
.withFailureHandler(() => {
document.getElementById('recentLpPlans').innerHTML =
'<p class="empty">履歴を読み込めませんでした。</p>';
})
.getRecentLpPlans();
}
function renderRecentLpPlans(plans) {
const container = document.getElementById('recentLpPlans');
if (!plans || plans.length === 0) {
container.innerHTML =
'<p class="empty">まだ履歴がありません。</p>';
return;
}
container.innerHTML = plans
.map((item) => {
return `
<div class="recent-item">
<p class="recent-title">${escapeHtml(item.title)}</p>
<p class="recent-meta">
${escapeHtml(item.createdAt)}|${escapeHtml(item.tone)}
</p>
<button class="small-button" type="button" onclick="showLpPlan('${encodeURIComponent(item.result || '')}')">
LP構成を見る
</button>
</div>
`;
})
.join('');
}
function showLpPlan(encodedText) {
resultBox.textContent = decodeURIComponent(encodedText);
}
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
loadRecentLpPlans();
</script>
</body>
</html>
7. 保存する
コードを貼り付けたら保存します。
Command + S
Windowsの場合は、
Ctrl + S
です。
8. Webアプリとして公開する
右上の「デプロイ」を押します。
デプロイ
↓
新しいデプロイ
種類は、次を選びます。
ウェブアプリ
設定は次の通りです。
| 項目 | 設定 |
|---|---|
| 説明 | 6-7 AI LP構成メーカー |
| 次のユーザーとして実行 | 自分 |
| アクセスできるユーザー | 自分のみ |
デプロイ後に表示されるURLを開くと、LP構成メーカーが表示されます。
9. 動かしてみる
画面が開いたら、まず「サンプル入力」を押します。
そのあと、次のボタンを押します。
LP構成を作る
少し待つと、右側にLP構成が表示されます。
スプレッドシートには、自動で LP構成履歴 シートが作られ、生成結果が保存されます。
10. 生成結果の見方
生成されたLP構成では、まずこの3つを確認します。
最初の一言で興味を引けるか
企画の魅力が3つに整理されているか
最後に何をすればよいか分かるか
LPは、情報をたくさん並べる場所ではありません。
見る人が、自然にこう進めることが大切です。
気になる
↓
分かる
↓
良さそう
↓
見てみよう
この流れができていれば、LP構成として十分です。
11. うまく出ない時
見出しが普通すぎる
プロンプトに次を追加します。
・見出しは少し印象に残る言葉にする
文章が長すぎる
次を追加します。
・本文は1セクション2〜3行以内にする
行動が分かりにくい
次を追加します。
・最後のボタン文言を具体的にする
例です。
詳しく見る
参加方法を見る
おすすめを見る
申し込む
画像や動画の使い方が弱い
次を追加します。
・画像と動画をどのセクションに置くか具体的に書く
まずはコード全体ではなく、プロンプトだけ直せば大丈夫です。
12. 今日の提出物
この回の提出物は、次の3つです。
1. WebアプリURL
2. 生成されたLP構成のスクリーンショット
3. スプレッドシートの履歴スクリーンショット
余裕がある人は、生成されたLP構成をもとに、FigmaやCanva、Googleスライドでラフデザインを作ってください。
最低限は、これでOKです。
1ページLPの流れが作れた
13. チェックリスト
| チェック | 内容 |
|---|---|
| スプレッドシートを作った | |
| Apps Scriptを開いた | |
| GEMINI_API_KEYを確認した | |
| Code.gsを貼り付けた | |
| index.htmlを作った | |
| index.htmlを貼り付けた | |
| Webアプリとしてデプロイした | |
| WebアプリURLを開いた | |
| サンプル入力を押した | |
| LP構成を作るを押した | |
| LP構成が表示された | |
| 履歴がスプレッドシートに保存された |
まとめ
今回は、企画を1ページのLP構成にまとめるアプリを作りました。
LPづくりで大切なのは、最初からきれいなデザインを作ることではありません。
まずは、流れを作ることです。
ファーストビュー
↓
企画の説明
↓
魅力・特徴
↓
画像・動画
↓
最後の案内
この流れがあるだけで、Webページは作りやすくなります。