2023年07月11日
さて、3回目です。
それとなく完成形が見えてきた感じです。
それではいきましょう。
今回は肝となる、ブログの記事内容表示部分です。
Next.jsの目玉と言っても過言ではない、SSGに関する部分があります。
このようなディレクトリを作成します。
idの周りの[]も必要です。
これがあることで、<app/blog/hogehoge>とか、<app/blog/fugafuga>のようなURLでアクセスがあった場合、このディレクトリが読み込まれます。
ここも、他のディレクトリ同様、layout.tsxと対になるpage.tsxを設置します。
それぞれ中身を見てみましょう。
export default async function BlogDetailLayout({
children,
}: {
children: React.ReactNode;
}) {
return <div>{children}</div>;
}
layout.tsxはなにもありません。
では、page.tsxを見てみましょう。
import { getBlogDetail, getBlogs } from "@/libs/microcms";
import { BlogPostType } from "@/types/blogPost";
import Image from "next/image";
import React from "react";
import parse from "html-react-parser";
import { format } from "date-fns";
import styles from "../../styles/page.module.css";
import Link from "next/link";
import "../../styles/code.css";
import { ArrowUturnLeftIcon } from "@heroicons/react/24/solid";
import { M_PLUS_Rounded_1c } from "next/font/google";
const mPlus400 = M_PLUS_Rounded_1c({
weight: ["400"],
subsets: ["latin"],
display: "swap",
});
// for SSG
export async function generateStaticParams() {
const contents = await getBlogs();
const id = contents.map((content) => {
return {
id: content.id,
};
});
return [...id];
}
export default async function BlogDetail({
params: { id },
}: {
params: { id: string };
}) {
const blogDetail: BlogPostType = await getBlogDetail(id);
return (
<>
<div className={mPlus400.className}>
<div className={styles.detailArea}>
<div className={styles.detailTitle}>
<h1>{blogDetail.title}</h1>
</div>
<div className={styles.detailDate}>
<p>{format(new Date(blogDetail.createdAt), "yyyy年MM月dd日")}</p>
</div>
<div className={styles.detailImage}>
<Image
src={blogDetail.eyecatch?.url || "/no-image.png"}
// width={320}
// height={200}
layout="fill"
objectFit="contain"
alt={blogDetail.title}
/>
</div>
<div className={styles.detailText}>{parse(blogDetail.content)}</div>
<div>
<Link href="/blog" className={styles.detailBack}>
<ArrowUturnLeftIcon className="w-5 h-4 m-0 p-0" />
<span>戻る</span>
</Link>
</div>
</div>
</div>
</>
);
}
この中で大切なのが、generateStaticParamsです。
Next.js 12までは、getStaticPathsとgetStaticPropsを使ってSSGを実装していましたが、Next.js 13では、こちらを使います。
戻り値が今までと異なりますのでご注意ください。
これで、各ブログ記事は、ビルド時に静的に生成されます。
結果、リクエストがあった際には、生成済みHTMLだけがレスポンスで返されます。
表示、めっちゃ早いです。
もし、存在しないページにアクセスがあった場合は、自動的にapp/blog/id]/not-found.tsxが読まれます。
今は何も書いていませんが、適当にカスタマイズしてみてください。
import React from "react";
const NotFound = () => {
return <div>blog not found...</div>;
};
export default NotFound;
中身はホント空です。returnの中に、not found時に表示したい内容を書きましょう。
私の場合、今後ゆっくりと実装していこうと思います。
さて、これでブログ部分については、一応完成です。
CSSについては、いろいろと考え方があるかと思いますので、どれが正解とかありませんが、以下に貼っておきます。
tailwind CSSを使用していますので、合わせてご理解いただければと思います。
/* blog list page for pc */
@media screen and (min-width: 750px) {
.topTitle {
font-size: 5rem;
}
.topSubTitle {
font-size: 2rem;
text-align: center;
}
.topArrow {
/* text-align: center; */
display: flex;
justify-content: center;
align-items: center;
}
.aboutMain {
width: 70%;
text-align: left;
margin: 0 auto;
}
.topHeroImage > img {
position: static !important;
width: 100% !important;
height: auto !important;
margin: 0 auto;
}
.aboutTitle {
font-size: 3rem;
}
.aboutArrow {
/* text-align: center; */
display: flex;
justify-content: center;
align-items: center;
}
.aboutPara {
line-height: 1.7rem;
}
.blogCard {
display: block;
margin: 30px;
width: 100%;
border: 1px solid #ccc;
border-radius: 20px;
box-shadow: 0px 3px 5px -1px #999;
transition: all 0.5s ease-in-out;
}
.blogCard:hover {
box-shadow: none;
}
.cardWrap {
display: flex;
}
.cardWrap .cardThumnail img {
border-radius: 20px 0 0 20px;
}
.cardWrap .cardText {
display: block;
width: 100%;
position: relative;
}
.cardWrap .cardTitle {
font-size: 2rem;
margin-top: 1rem;
margin-left: 0.7rem;
}
.cardWrap .createdate {
font-size: 0.9rem;
color: #ccc;
position: absolute;
bottom: 0;
left: 0.7rem;
}
.navbar {
height: 5rem;
text-align: center;
background-color: antiquewhite;
line-height: 5rem;
position: fixed;
width: 100%;
z-index: 9999;
}
.navText {
font-size: 2rem;
display: inline;
margin-left: 3rem;
}
.navText.firstNav {
margin-left: 0;
}
.footer {
height: 5rem;
line-height: 5rem;
text-align: center;
background-color: antiquewhite;
}
.blogListTitle {
font-size: 2rem;
margin-bottom: 0;
}
.detailArea {
padding-top: 6rem;
width: 100%;
text-align: center;
}
.detailTitle {
font-size: 2rem;
position: relative;
padding-bottom: 20px;
font-size: 26px;
text-align: center;
}
.detailTitle:after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-style: solid;
border-width: 10px 6px 0 6px;
border-color: #b99a00 rgba(0, 0, 0, 0) rgba(0, 0, 0, 0) rgba(0, 0, 0, 0);
}
.detailDate {
font-size: 0.9rem;
}
.detailImage > img {
position: static !important;
width: 70% !important;
height: auto !important;
margin: 3rem auto;
}
.detailText {
width: 70%;
margin: 0 auto;
text-align: left;
line-height: 1.6rem;
}
.detailText h1 {
font-size: 3rem;
margin: 1.5rem 0;
}
.detailText > h2 {
font-size: 1.2rem;
margin: 1rem 0;
position: relative;
padding-bottom: 10px;
font-size: 26px;
}
.detailText > h2:after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 8px;
background-image: repeating-linear-gradient(
45deg,
#b4a983 0px,
#b4a983 1px,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0) 50%
);
background-size: 8px 8px;
}
.detailBack {
display: flex;
justify-content: center;
align-items: center;
}
}
@media screen and (max-width: 749px) {
.navbar {
height: 2rem;
background-color: bisque;
text-align: center;
line-height: 2rem;
}
.navbar a {
margin-left: 1rem;
}
.main {
padding: 5px !important;
}
.topHeroImage > img {
position: static !important;
width: 100% !important;
height: auto !important;
margin: 20px auto;
}
.topTitle {
text-align: center;
font-size: 1.5rem;
}
.topSubTitle {
text-align: center;
}
.topArrow {
display: none;
}
.topPMargin {
margin-top: 3rem;
}
.aboutMain {
padding: 5px !important;
}
.blogListCard {
margin-top: 3rem;
}
.cardTitle {
font-size: 1.5rem;
}
.createdate {
font-size: 0.9rem;
color: #777;
}
.detailArea {
width: 75%;
}
.detailTitle {
font-size: 2rem;
}
.detailDate {
margin-bottom: 1rem;
}
.detailImage > img {
position: static !important;
width: 100% !important;
height: auto !important;
margin: 20px auto;
}
.detailArea {
width: 90%;
margin: 0 auto;
}
.detailText h2 {
font-size: 1.3rem;
margin: 1rem 0;
}
}
あとは、ヘッダーやらフッターやらありますが、これも各自で実装していただければと思います。
さて、今後に向けての積み残しです。
このあたりを実装していこうと思っています。
では、今回はmicroCMSとReact + Next.js13でのブログ作成までの道のりでした。
ありがとうございました。