目次
はじめに
前回まではAstroのフレームワークについて公式ドキュメントを参考に学習を進めてきた。Astroの仕組みを理解したいま、あらためてastro-notion-blogのソースコードを見返して、意味不明だった構造をひもといていきたい。
Pagesの構成
src/pages/index.astro
ブログのトップページとなるファイル。
src/pages/[slug].astro
ブログの記事毎のページを作成するためのファイル。動的ルーティングを利用して、slugをURLにしている。slugはgetStaticPathsで取得するようになっている。
export async function getStaticPaths() {
const posts = await getAllPosts()
return posts.map((post: interfaces.Post) => ({ params: { slug: post.Slug } }))
} src/pages/posts/page/[page].astro
トップページに表示されるブログ記事の一覧が1ページに収まらない場合に、複数ページに分けて一覧を作成するためのファイル。[page]には1〜Nの数字が入り、数字が小さいほど最新の記事一覧になる。
export async function getStaticPaths() {
const numberOfPages = await getNumberOfPages()
let params = []
for (let i = 2; i <= numberOfPages; i++) {
params.push({ params: { page: i.toString() } })
}
return params
} ちなみに、1ページに表示できる記事の最大数は、NUMBER_OF_POSTS_PER_PAGEで定義されている。
export const NUMBER_OF_POSTS_PER_PAGE = 10 src/pages/posts/tag/[tag].astro
特定タグをもつ記事一覧の1ページ目を作成するためのファイル。
src/pages/posts/tag/[tag]/page/[page].astro
特定タグをもつ記事一覧が1ページに収まらない場合に、複数ページに分けて一覧を作成するためのファイル。ここでフォルダ名にも動的ルーティングが使えることを知りました。
[page]には1〜Nの数字が入り、数字が小さいほど最新の記事一覧になる。[tag].astroとのコードに大差ないが、タイトルにページ番号がなどの違いが見られる。
export async function getStaticPaths() {
const allTags = await getAllTags()
let params = []
await Promise.all(
allTags.map((tag: SelectProperty) => {
return getNumberOfPagesByTag(tag.name).then((numberOfPages: number) => {
for (let i = 2; i <= numberOfPages; i++) {
params.push({ params: { tag: tag.name, page: i.toString() } })
}
})
})
)
return params
} レイアウトの利用
Layout.astro
全てのページからimportされて使われているレイアウトファイル。
graph TD
Layout("Layout.astro")
Layout --> |import|Index("index.astro")
Layout --> |import|Page("posts/page/[page].astro")
Layout --> |import|Slug("posts/[slug].astro")
Layout --> |import|Tag("posts/tag/[tag].astro")
Layout --> |import|TagPage("posts/tag/[tag]/page[tag].astro")
Layout内にはslotが2つあり、<slot>にname属性を埋め込むことで任意のslotに差し込むことが出来るということみたい。例えば、次のように3つのslotを埋め込んだ場合、nameがないslotがデフォルトとなり、name指定した場合に、一致するnameのslotにタグが埋め込まれることになる。
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 名前なしのデフォルトスロット -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div> ---
import Layout from '../Layout.astro';
---
<Layout>
<h1 slot="header">headerのslotに埋め込むタグ</h1>
<p>デフォルトのslotに埋め込むタグ</p>
<p slot="footer">footerのslotに埋め込むタグ</p>
</Layout> 検索ボタンの仕組み
SearchButton.astro
左側のメニューに表示される検索ボタンはどのような仕組みになっているのか確認してみた。検索はSearchButton.astroをインポートしており、ボタンを押したときの動作もここで定義されている。また、ボタンだけでなくキーボードでも検索画面が表示されるようにkeydownのイベントが登録されていることが分かる。
document.addEventListener('DOMContentLoaded', () => {
function handleKeydown(event) {
if (event.keyCode === 75 && event.metaKey) {
// Ctrl+K or Cmd+K
document.querySelector('.open-search-modal').click()
}
}
document.addEventListener('keydown', handleKeydown)
}) SerchModal.astro
SearchButtonを押したとき非表示となっているSerchModalが表示される仕組みとなっているが、SearchButton.astroには検索ボタンが押されたときのイベントハンドルが見当たらない。探したところ、SerchModal.astroに記述されているのを発見した。
Array.from(document.getElementsByClassName('open-search-modal')).forEach(
(element) => {
element.addEventListener('click', openModal)
}
) 検索処理は、事前に読み込んでおいたRSSフィードから検索文字列の比較を行い、部分一致する記事を検索結果の窓に表示する仕組みとなっている。
async function fetchRSS() {
const url = new URL(location.href)
const port = url.port ? `:${url.port}` : ''
const res = await fetch(
`${url.protocol}//${url.hostname}${port}/feed`
)
if (res.status !== 200) {
console.log(res.status)
throw new Error('Failed to fetch RSS feed')
}
//以下省略
} function search(query) {
const results = []
feedItems.forEach((item) => {
if (item.title.includes(query) || item?.description?.includes(query)) {
results.push(item)
}
})
return results
} RSSフィード
検索に利用しているRSSフィードは、”src/pages/feed.ts”で記述されている。astroのRSSフィード生成に関しては公式にも記載されている。ちなみに、生成されたSSフィードは、<ルートURL>/feedで確認することが出来る。
まとめ
今回は、astro-notion-blogのページ構造や検索の仕組みを確認した。その中で、以下のことを新たに学ぶことができた。
- フォルダ名に動的ルーティングが使えること
- レイアウトにslotを複数記述できること
- RSSフィードの生成方法
次回はレイアウトを実際に修正して、ブログの見た目を変更することに挑戦していきたい。
