この記事は、Zennに投稿した以下の記事の再投稿になります。
はじめに
AstroとNewtで個人ブログを作成する記事の第二回目になります。
- Astroとは?Newtとは?
 - チュートリアルの紹介
 - 使ったテンプレートの紹介
 
第二回
- サイトの構築手順
 
- サーバー構築手順
 - デプロイ→サイト公開!
 
今回は、サイトの構築手順について書いていきます。
対象者
- バックエンドの実装をせずにサイト構築したい方
 - 個人サイトが作りたい方
 - AstroのこともNewtのことも何となく知っている方
 - Astroのテンプレートの使い方を知りたい方
 
動作環境・前提
| バージョン | |
|---|---|
| Mac | 13.x | 
| npm | 8.11.0 | 
| Astro | 2.8.5 | 
| newt-client-js | 3.2.4 | 
サイトの構築手順
Newtのチュートリアルをやってみる
実際に作り始める前に、まずはチュートリアルをやってみましょう。
もうやってるよ!って方は、次に進んでもらって大丈夫です。
以下は、Newtの公式サイトにあるチュートリアルです。
Astroのセットアップから始まって、Newtのセットアップ、NewtのAPIクライアント作成とそれをAstroで使う方法、記事の表示方法といった流れで、今回やりたいことが全部詰まってます。
いきなりオリジナルのサイトを作り始めても悪くはないと思いますが、開発の流れを掴むためにもまずは一通りチュートリアルをやってみることをお勧めします。
1時間もあれば全部できると思うので、気軽にサクッと触ってみてください。
テンプレートを選ぶ
ここでは、ブログサイトに使用したいテンプレートを選んでいきましょう。
以下が、Astroのテンプレート一覧です。
中には有料の物もありますが、無料でもオシャレな物がたくさんありますね。ありがたや…!
僕は今回以下のテンプレートを使用しました。
選んだ理由は、めっちゃシンプルでカスタムしやすそうだったからです。
僕、HTMLとCSSは読めるし書けるんですが、JSは簡単なものしか読めないし書けなくて、あんまりオシャレすぎるとコード難しくて全然カスタマイズする余地がない!てなりそうだったので…
皆さんも、コードをしっかり確認してご自身のレベルに合ったテンプレートを選んでみてください。
新しいプロジェクトを作成する
さて、いよいよブログサイトの開発に着手していきます。
まずは、使いたいテンプレートのGithubを開きましょう。
大体、GithubのREADMEにテンプレートを使った新規プロジェクトの作り方が説明してあると思うので、それに従ってプロジェクトを新しく作成してください。

コマンドを実行したら、チュートリアルの時と同じように色々質問されると思うので、こちらもチュートリアルの時と同じように答えてあげてください。
プロジェクトが作成できたら、開発サーバーを立ち上げて、http://localhost:3000/にアクセスしてみましょう。
今度はチュートリアルの時とは異なり、選んだテンプレートのLive demoのような画面が出てきたと思います。

※ テンプレートによっては異なる場合があります。
この画面が見れたら、プロジェクトの作成は完了です!
カスタマイズ① Newtと連携させる
それでは、テンプレートをカスタマイズしていきましょう。
まずは、Newtで書いた記事を表示するために、Newtと連携させていきます。
連携させる作業自体は、ほぼチュートリアルのまんまで大丈夫です。
ただし、チュートリアルの時とはディレクトリ構成が変わっていると思うので、照らし合わせながら上手くやってください。
以下は僕が行ったカスタマイズの例です。
デフォルトのディレクトリ構造はこんな感じでした(README参照)。
/
├── public
└── src
    ├── components
    │   ├── layouts
    │   └── utilities
    ├── content
    ├── pages
    └── style
この内、以下のファイルを追加・修正して、Newtの記事を表示させることに成功しました。
/
├── public
└── src
    ├── components
    │   ├── blog
+   │   │   └── preview.astro <- 修正
    │   ├── layouts
    │   └── utilities
    ├── content
+   ├── lib
+   │   └── newt.ts  <- 追加
    ├── pages
    │   ├── articles
+   │   │   └── [slug].astro <- 追加
+   │   └── index.astro <- 修正
    └── style
各ファイルのコードも置いておきます。
(分量が多いためアコーディオンにしておきます。適宜確認したいファイルのものを展開してご覧ください。)
/src/components/blog/preview.astro
---
import type {Article} from '../../lib/newt';
import Date from "@components/utilities/Date";
const article: Article = Astro.props.article
---
<div class="cover-box">
	<a class="slug-link" href={`/articles/${article.slug}`}>
		<h2>
			{article.title}
			<p class="date" style="font-size: 50%;"><Date date={article.first_date}/></p>
			<img src={article.coverImage["src"]} />
		</h2>
		<div class="dark-cover"></div>
	</a>
</div>
<style>
	h2 {
		background-color: hsl(188 8% 95%);
		width: auto;
		padding: 5%;
	}
	.slug-link {
		color: hsl(188 5% 27%);
		text-decoration: none;
	}
	.cover-box {
		position: relative;
	}
	img {
		margin-top: 3%;
		width: 100%;
		height: 300px;
		object-fit: cover;
	}
	.dark-cover {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 1;
		opacity: .1;
	}
	.dark-cover:hover {
		background-color: black;
	}
</style>
/src/lib/newt.ts
import { createClient } from "newt-client-js"
export interface Article{
    title: string
    slug: string
    body: string
    coverImage: object
    tags: []
}
export const newtClient = createClient({
    spaceUid: import.meta.env.NEWT_SPACE_UID,
    token: import.meta.env.NEWT_CDN_API_TOKEN,
    apiType: 'cdn',
})
/src/pages/articles/[slug].astro
---
import Blog from "@layouts/blog";
import Date from "@components/utilities/Date";
import {newtClient} from '../../lib/newt'
import type {Article} from '../../lib/newt'
export async function getStaticPaths({ rss }) {
    const {items: articles} = await newtClient.getContents<Article>({
        appUid: 'blog2',
        modelUid: 'article',
        query: {
            select: ['title', 'meta', 'slug', 'body', 'coverImage', 'tags', 'first_date', 'update_date'],
        },
    })
    return articles.map((article)=>({
        params: {slug: article.slug},
        props: {article},
    }))
}
// Destructure to get both `Astro.props.post` and `Astro.props.post.Content`
const { article } = Astro.props;
---
<Blog title={article.meta.title} description={article.meta.description}>
	<div class="post-header container">
		<h1 class="post-title">{article.title}</h1>
		<div class="post-date">
			<p>公開日:<Date date={article.first_date}/></p>
			{
				article.update_date === "" ? (
					<p></p>
				) : (
					<p>更新日:<Date date={article.update_date}/></p>
				)
			}
		</div>
		<img src={article.coverImage["src"]} />
	</div>
	<div class="content container">
        <!-- set:htmlはXSS攻撃の危険がある。今回はNewtで作成された記事以外を受け付けないので、安全としている -->
        <article set:html={article.body} />
	</div>
</Blog>
<style>
	.post-header {
		margin-top: 60px;
		margin-bottom: 40px;
	}
	.post-date {
		font-size: 0.75rem;
	}
	.content {
		margin-bottom: 40px;
	}
</style>
/src/pages/index.astro
---
import Blog from "@layouts/blog";
import Preview from "@components/blog/preview";
import {newtClient} from '../lib/newt'
import type {Article} from '../lib/newt'
const {items: articles} = await newtClient.getContents<Article>({
	// https://app.newt.so/romy-will-become-dragon/apps/blog2/settings/general に書いてあるやつ
	appUid: 'blog2',
	modelUid: 'article',
	query: {
		select: ['title', 'slug', 'body', 'coverImage', 'tags', 'first_date']
	},
})
---
<Blog title="ろみぃちゃんはドラゴンになりたい" description="ろみぃちゃんが雑多に書くブログ">
	<div class="content container">
		{articles.map((a) => <Preview article={a} />)}
	</div>
</Blog>
決してこれが正解、というわけではないので、ご自身のやりやすい方法でカスタマイズされるのが良いかと思います。
今後どんな感じにサイトを発展させていきたい、みたいなイメージが既にある方は、そこから逆算してディレクトリ構造を考えるとかでも良さそうですね。
カスタマイズ② 画面のレイアウトを整える
テンプレートのままではオリジナリティが出ないので、ちょこっとデザインを変えてカスタマイズしていきましょう。
ここは特に解説することはないです。
皆さんの思うように、存分にカスタマイズしちゃってください!
一例として、僕が作ったブログサイトの記事一覧画面をお見せしますね。

ちょっと恥ずかしいのでモザイクかけました笑
どうせサイト開いたらすぐ見えるんですけどね、、
テンプレートのLive demoと比べると、大分味気なさが無くなっていると思います!
タイトルの横にSNSのアイコン置いてリンク張るやつ、やってみたかったんですよね〜 満足。
皆さんも、是非「こんなデザインにしたい」「こういうの付けてみたい」を形にしていってください!
最後に
今回は、ローカル環境で新規プロジェクトを構築し、テンプレートをカスタマイズするところまでやりました。
どうでしょう?結構簡単だったんじゃないでしょうか?
次回はサーバーの構築をやりますので、是非そちらもご覧ください!