Next.js Tutorial -- .md files の ヘッダーデータ のリスト表示と CSS
starter
ブラウザキャッシュをクリアして en/ に飛ばされる問題を解決し、 空の next app を動かした
Hello Next
エディタを使うので VScode をセットアップする
pages/ に posts/first-post.js を作って
export default function FirstPost() { return ( <div> <h1> First Post </h1> </div> ) }
これでだすと return の中身が pages/post/first-post にアクセスした時に出る
root から post/first-post にリンクする
import Head from 'next/head' import Link from 'next/link' export default function Home() { return ( <div className="container"> <Head> <title>Next.js Blog Tutorial</title> </Head> <h1> This is posts list page </h1> <h2> <Link href='/posts/first-post'> <a> READ MY FIRST POST </a> </Link> </h2> </div> ) }
- first-post.js に root から飛べるようにリンクを置く
first post から root にもリンクする
import Link from 'next/link' import Head from 'next/head' export default function FirstPost() { return ( <div> <Head> <title> First Post </title> </Head> <h1> This is my first post </h1> <h2> <Link href='/'> <a> Go to posts list </a> </Link> </h2> </div> ) }
- これで root ( / ) からも
posts/first-post
からも相互的に飛べるようになった。
16:40
styling
スタイリングをする
- pages/ の外、 root 直下に components/ を作る
- そこに layout.js をお
Layout を作る
export default function Layout({children}) { return ( <div className='container'> {children} </div> ) }
- 中身を container class のついた div でくくるだけの Layout を作って出す
FirstPost を Layout で wrap する
import Link from 'next/link' import Head from 'next/head' import Layout from '../../components/layout' export default function FirstPost() { return ( <Layout> <Head> <title> First Post </title> </Head> <h1> This is my first post </h1> <h2> <Link href='/'> <a> Go to posts list </a> </Link> </h2> </Layout> ) }
- その Layout を FirstPost で使う
- Chrome の divtool で確認すると div class container で wrap されてるのを確認できる
root も Layout で wrap する
import Head from 'next/head' import Link from 'next/link' import Layout from '../components/layout' export default function Home() { return ( <Layout> <Head> <title>posts list</title> </Head> <h1> This is posts list page </h1> <h2> <Link href='/posts/first-post'> <a> READ MY FIRST POST </a> </Link> </h2> </Layout> ) }
- スタイリングを統一するために root も Layout でくくる。
17:00
Layout の module を作る
いい感じでカードにしたいから自分で考える
Tutorial のはレスポンシブにならない中央に寄らない
前試したこれを使う
- components/layout.module.css を作成
.wrapper { display: grid; height: 100vh; margin: 0; place-items: center center; }
- ど真ん中に揃えるようにスタイリングする
Layout で layout.module.css の wrapper を使う
import styles from './layout.module.css' export default function Layout({children}) { return ( <div className={styles.wrapper}> {children} </div> ) }
- layout.module.css を styles として一括して読み込む
- その中の
wrapper
を読み込んで div の className にして children を wrap する
- root が真ん中揃えにできた
- FirstPost もできた
- SP でも見えはしてる
文字の感覚がやばいけどな!!!!!!!
とりあえず一応の 全体レイアウトはできた!
17:20
global styling
さっきはコンテナ枠自体のスタイリングをした
今回は全てに共通するスタイリングをする
root の pages/ に _app.js
を作成する
これが root なので真っ先に読み込まれることになる
export default function App( { Component, pageProps }) { return <Component {...pageProps}/> }
ここに App コンポーネントを作り
1st 引数の Component の value ではない内部?に
2nd 引数の pageProps を入れる
tips spread attiributes
const hogeProps = { hoge: "fuga" }
— たふみ (@CreatorQsF) January 4, 2021
っていうのがあるとしたら、
<HogeComponent hoge={hogeProps.hoge} />
と
<HogeComponent {...hogeProps} />
は同じ意味だって感じです。
_app.jsxでconsole.logしたければ、returnの前に書けば表示されません?
<HogeComponent hoge={hogeProps.hoge} /> <HogeComponent {...hogeProps} />
は同じ意味らしい
function App1() { return <Greeting firstName="Ben" lastName="Hector" />; } function App2() { const props = {firstName: 'Ben', lastName: 'Hector'}; return <Greeting {...props} />; }
App2 では props の中身を一度定義して、変数で Greeting に渡せる
global styling 続き
import '../styles/global.css' export default function App( { Component, pageProps }) { return <Component {...pageProps}/> }
先ほどの pages/_app.js/App で
styles/global.css を読み込むようにする
global.css を作る
a { color:#0070f3; text-decoration: none; } a:hover { text-decoration: underline; }
リンクのところだけ、色を青にして、アンダーラインを無くして
マウスカーソルがホバーしてるときだけアンダーラインが出るようにする
_app をいじったので server を再起動すると
デフォルト
ホバー時
として css が反映されている
もちろん、さっきの post list のページだけでなく、 First Post のページでも反映されている。
共通 CSS なら layout.css.module.css の container に書けばいいと思ってしまうが、こちらは Layout コンポーネントを使わない場合でも適用されて欲しい、もっと汎用的なアプリ全体のスタイリングだと解釈する。
1/4 12:17
Polish CSS
省略
SSG
getStaticProps とは
Static Generation with and without Data - Pre-rendering and Data Fetching | Learn Next.js
export default function Home(props) { ... } export async function getStaticProps() { // Get external data from the file system, API, DB, etc. const data = ... // The value of the `props` key will be // passed to the `Home` component return { props: ... } }
使うコンポーネントの中で getStaticProps
を使用し
その中で API や DB などからデータをとってきて
return で props に渡す事で
Essentially, getStaticProps allows you to tell Next.js:
“Hey, this page has some data dependencies
— so when you pre-render this page at build time,
make sure to resolve them first!”
Next.js に 「このページはデータの依存性があるから、事前描画をビルド時に するとき、先にそのデータの依存性を解決してくれ」
と伝えることができる
Insert Blog Data
Blog の記事データを挿入する。マークダウンで。
/pages/posts/ とは別に、/posts/ をルートから作成する
その中に
pre-rending.md
--- title: 'Two Forms of Pre-rendering' date: '2020-01-01' --- Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page. - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request. - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**. Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
と
ssg-ssr.md
--- title: 'When to Use Static Generation v.s. Server-side Rendering' date: '2020-01-02' --- We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request. You can use Static Generation for many types of pages, including: - Marketing pages - Blog posts - E-commerce product listings - Help and documentation You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation. On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request. In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
の2つのデータを用意する
次にこの /posts/hogehoge.md
のファイルを index.js で読み込めるようにする
titile, date, url(id) を表示するようにする
index.js で マークダウンファイルを読み込む
必要な npm ライブラリとして
npm i gray-matter
で gray-matter を入れる
fs や path 自体は 既に Next.js のプロジェクトで入っている
このライブラリを使ったライブラリコンポーネントとして
/lib/posts.js
を用意する
中身には
import fs from 'fs' import path from 'path' import matter from 'gray-matter'
fs, path, gray-matter を持ってくる
このままだとfs が読み込めないので
package.json で
"browser": { "fs": false },
これを書く(なぜかは不明, 後回しにする)
const postsDirectory = path.join(process.cwd(), 'posts') console.log('postsDirectory: ',postsDirectory)
postsDirectory として 現在のプロセスのファイル構造?に posts を足して表示する
index で lib/post を読み込み process.cwd() をみる
import { getSortedPostsData } from '../lib/posts'
export function getSortedPostsData() {
で宣言した関数は、デフォルトじゃないから
import { getSortedPostsData } from '../lib/posts'
で { } で囲って 持ってくる、覚えてる
読み込んだ時点でさっきの fileNames が動き
postsDirectory: /posts
が ブラウザで index にアクセスすると console に表示される。
先ほどの lib/post で
console.log('process.cwd: ', process.cwd() ); const postsDirectory = path.join(process.cwd(), 'posts'); console.log('postsDirectory: ',postsDirectory);
こうして process.cwd()
と それに 'posts' を join させた結果を見てみる
すると index では
posts.js?9754:5 process.cwd: / posts.js?9754:7 postsDirectory: /posts
となる。
また今は root にあたる pages/index で使用したが
同じ ライブラリ、 lib/posts を
pages/posts/first-posts で使用する
import {getSortedPostsData} from '../../lib/'
すると console では
webpack-internal:///./lib/posts.js:12 process.cwd: / webpack-internal:///./lib/posts.js:14 postsDirectory: /posts
となり、変わらず root が起点になっているので、ブラウザで読んでいる位置とは関係ないことがわかった
process.cwd() は起点の root の位置のみを表す。 単に文字列の /
にしないのはドメインによって変化する可能性があるからだろう
getSortedPostsData() で マークダウンファイルを取得する
次に getSortedPostsData() , 並び替えられた投稿データを取得する関数を作
前半で 投稿たちのファイル名をとってパスに入れて
コンテンツをとってきて matter でパースする
最後に return のスコープのなかで 並び替えて return する。
export function getSortedPostsData() { // }
この中の処理を書いていく
まだ console.log では表示できない。
const fileNames = fs.readdirSync(postsDirectory);
まずさっき取得した {root}/posts/ を fs を使って読み込み、配列を取得する
const allPostsData = fileNames.map(fileName => {
今取得した配列を展開し、一つづつ fileName の単数として処理する
allPostsData と言う変数を作る。中身は
const id = fileName.replace( /\.md$/, ''); console.log('id: ', id);
fileName と言う 一つのマークダウンファイルの名前から行末の .md を 何もないものに置換
const fullPath = path.join(postsDirectory, fileName);
変数 fullPath に最初に取得した root + /posts にそのファイル名を足したものを入れる
const fileContents = fs.readFileSync(fullPath, 'utf8')
変数 fileContents では 今の fullPath から utf8 で fileContents を読み込む
const matterResult = matter(fileContents);
matterResult では それに matter を通して、ヘッダー部分の情報を読み込んだものを返す
return { id, ...matterResult.data }
そして allPostData の返り値として id, matterRult の data 、中身を返すようにする
return allPostsData;
この後の日付での並び替えは一旦省略して、今の allPostData の結果を
getSortedPostsData() の返り値にする
この関数の全体はこうなった
export function getSortedPostsData() { const fileNames = fs.readdirSync(postsDirectory); console.table(fileNames); const allPostsData = fileNames.map(fileName => { const id = fileName.replace( /\.md$/, ''); console.log('id: ', id); const fullPath = path.join(postsDirectory, fileName); const fileContents = fs.readFileSync(fullPath, 'utf8'); console.table('fileContents: ', fileContents); const matterResult = matter(fileContents); console.table('matterResult', matterResult) return { id, ...matterResult.data } }) return allPostsData; }
これですぐ使えて結果が見えるわけではなく、getStaticProps に渡す必要がある
index で getStaticProps に渡して 事前生成してもらう
まず index で getStaticProps に渡す
getStaticProps は Next の基礎機能だからか、import しなくても入っている
Home の上部で getStaticProps を使用する
export async function getStaticProps() { const allPostsData = getSortedPostsData() return { props: { allPostsData } } }
先ほど Markdown ファイルたちから id と中身のセットの配列にパースした getSortedPosts を index の getStaticProps で props : { hoge } としてセットする
これでビルド時に先に読み込んでくれることになるはず
index/Home で データを展開する
export default function Home( { allPostsData } ) {
まず getStaticProps の props に渡した allPostsData を Home で引数に
( { hoge } ) の形で読み込む
return ( { allPostsData.map( ( { id, date, title, } ) => ( <h2 key={id}> title: {title} <br/> id: {id} <br/> date: {date} <br/> </h2> )) } )
return 部分で allPostsData を展開し
( { hoge, hoge, hoge, } ) の形で中身の id, date, titile を使い
タグに挟み込む。
これで
CSS はおかしいけど、マークダウンのファイルたちの
ヘッダー部分のタイトル、ファイル名、ヘッダー部分の日付、
が index で表示できた。
中身のコンテンツの表示はこれから。
lib/posts でさっき出したもとを確認
const allPostsData = fileNames.map(fileName => { const id = fileName.replace( /\.md$/, ''); console.log('id: ', id); const fullPath = path.join(postsDirectory, fileName); const fileContents = fs.readFileSync(fullPath, 'utf8'); console.log('fileContents: ', fileContents); const matterResult = matter(fileContents); console.log('matterResult', matterResult) return { id, ...matterResult.data } })
allPostsData はこうなっている。
path からコンテンツを読み取り、それを matter にかけてその data を渡している
id は fileName を変換しているが、それ以外の titile, date, は出てきてすらいない。
matter がよしなにやっているってことだろう。
読み込んだ元のファイルは
--- title: 'aaaa' date: '2020-01-12' --- ああああああああああ
こんな感じ。
この 「あああああああ」の部分に名前がない。
先に進んで詳細部分のページでみると
export async function getPostData(id) { const fullPath = path.join(postsDirectory, `${id}.md`) const fileContents = fs.readFileSync(fullPath, 'utf8') // Use gray-matter to parse the post metadata section const matterResult = matter(fileContents) // Use remark to convert markdown into HTML string const processedContent = await remark() .use(html) .process(matterResult.content) const contentHtml = processedContent.toString() // Combine the data with the id and contentHtml return { id, contentHtml, ...matterResult.data } }
remark を使って読み取るらしい。
matter では無理そう
さらに表示するときは
export default function Post({ postData }) { return ( <Layout> {postData.title} <br /> {postData.id} <br /> {postData.date} <br /> <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} /> </Layout> ) }
危険に HTML の内部に入れるやつを使ってぶち込むようだ。 とりあえずまだ無理。
CSS デフォルトに
見た目がダメなので、CSS を規定のにもどす
import styles from './layout.module.css' export default function Layout({children}) { return ( <div className={styles.container}> {children} </div> ) }
Layout.js で container に当てるようにして
.container { max-width: 36rem; padding: 0 1rem; margin: 3rem auto 6rem; }
layout.module.css で
幅の最大を 36rem
文字と箱の枠の幅を 0, 高さを 1rem,
箱とその外の枠の、 上を 3rem, 幅を自動中央揃え、下を 6rem,
にする
これである程度マシになった。
ヘッダー部分の CSS を作る
.container { max-width: 36rem; padding: 0 1rem; margin: 3rem auto 6rem; } .header { display: flex; flex-direction: column; align-items: center; } .headerImage { width: 6rem; height: 6rem; } .headerHomeImage { width: 8rem; height: 8rem; } .backToHome { margin: 3rem 0 0; }
コンテナの中のヘッダー部分のレイアウトの CSS を追加する
ヘッダー全体 では flex の上から流すレイアウトで 縦の中央揃えをする
ヘッダーの画像は 幅も高さも 6rem
ホームでのヘッダー部分の画像は 幅も高さも 6rem に
リスト表示からホームに戻るリンクのマージンは 上 3rem 他 0
にする
サイズ別の CSS を作る
sytles/ に utils.module.css
機能たちのモジュール CSS を作る
.heading2Xl { font-size: 2.5rem; line-height: 1.2; font-weight: 800; letter-spacing: -0.05rem; margin: 1rem 0; }
まずは 2XL サイズの ヘッディング。h1, h2 のカスタムみたいなものだろう
文字サイズが 2.5rem,
行と行の感覚が 1.2 倍
フォントの太さが 800, 1.3 倍くらい
文字と文字の空白を 0.05 rem つめる
マージンは 左右 1rem で上下は 0
.headingXl { font-size: 2rem; line-height: 1.3; font-weight: 800; letter-spacing: -0.05rem; margin: 1rem 0; }
XL では
文字サイズが 2XL の 2.5 から2.0 に
行間が 1.2 から 1.3 に
文字の太さ、文字と文字の間隔、マージン、は同じスタイル
を適用
.headingLg { font-size: 1.5rem; line-height: 1.4; margin: 1rem 0; }
Large では
サイズが 1.5
行間が 1.4
文字の太さ、文字の間隔、を通常の大きさにして
マージンは同じスタイルを適用する
.headingMd { font-size: 1.2rem; line-height: 1.5; }
Medium では
1.2rem size
1.5 height
.borderCircle { border-radius: 9999px; } .colorInherit { color: inherit; } .padding1px { padding-top: 1px; } .list { list-style: none; padding: 0; margin: 0; } .listItem { margin: 0 0 1.25rem; } .lightText { color: #999; }
あとは
角を丸める
親と同じ色を使う
1px 上から pdg とる
リストの丸点をなくし、mgn, pdg, を全て 0 にする
リストの一つ一つの下の mgn のみ 1.25rem 空ける
グレーの文字色にする
の細かい スタイルをコピペした
Layout に ヘッダーとメタデータを入れる
import styles from './layout.module.css' export default function Layout({children}) { return ( <div className={styles.container}> {children} </div> ) }
children として受け取った コンポーネントに コンテナの クラス名がついた div で包むだけだった Layout から変更する
新しい Layout では
import Head from 'next/head' import Link from 'next/link' import styles from './layout.module.css' import utilStyles from '../styles/utils.module.css'
- Head, Link の Next 基礎機能を import
- Layout の基本 スタイリングを layout.module.css から import
- サイズ別などの 便利 スタイリングを utils.module.css から import
const name = 'Your Name' export const siteTitle = 'Next.js Sample Website'
header に表示する 著者の名前を定義
サイト自体のタイトルを 定義して (別ページでも使えるように?) export
Head, header, main, Link fo Home, を定義
export default function Layout({children, home}) { return ( <div className={styles.container}> <Head> </Head> <header> </header> <main> {children} </main> <Link href='/'> <a>BACK TO HOME</a> </Link> </div> ) }
基礎的な枠を先に作る
まず div コンテナで全体を包む
メタデータを入れるために Head を置く
ブログ情報を入れるための header を置く これは home の内容のあるなしで表示を分岐させる
本文を入れるための main を置く
home がある場合のみ現れる、root へのリンクを置く
Head の中身を書く
この辺りで ヘッダーの丸アイコン兼 ブラウザのタブアイコンを作る
<link rel="icon" href="/favicon.ico" /> <meta name='description' content='nextjs trial blog'/> <meta property='og:image' content={`https://picsum.photos/400/250`} /> <meta name='og:titile' content={siteTitle}/> <meta name='twitter:card' content='summary_large_image'/>
- Next の Link ではなく、小文字の link で favicon のリンクを貼る
- 説明として Next の練習ブログと記載
- OG 画像として 400x250 の適当な画像を読み込む
- OG タイトルとしてサイトタイトルを記載
- Twitter カードとして 大きい画像と 要約を記載
この次に Header 情報を書く。どのページにも表示されているプロフとか書くとこだと思う
私のブログで言うとここのこと
header を作る
layout.js の先ほどの
<header> {home? ( <> <img src='/images/profile.jpeg' className={` ${styles.headerHomeImage} ${utilStyles.borderCircle} `} alt={name} /> <h1 className={utilStyles.heading2Xl}> {name} </h1> </> ) : ( <div>hoge</div> )} </header>
引数の home コンポーネントがある場合は
- プロフ画像を ヘッダーの画像用途として layout css で 8 rem x 8rem にして
- 円にするときの便利 util CSS を使って 円にして
- 便利 CSS で 2XL サイズの h1 にして
- プロフィールの名前を出す
んで home がなければ違うものを出すって処理だな
index で <Layout home>
とすることで
<Layout>
だけだと
その心は Layout コンポーネントに 「これは Home (ブログのホーム画面) だから 著者の画像とタイトル出してあげてね」って伝えるってことかなあ
次の記事に続く