0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js 14 と microCMSで作る俺得RSSリーダー:開発編その3, レイアウト作成・RSS一覧取得編

Posted at

過去の記事

今回は基本的なレイアウトを作った後、microCMSに登録したRSSから一覧表示までを目標にやっていきます。

レイアウトの作成

ヘッダーの作成

Header.tsx
import React from 'react'
import styles from './Header.module.scss'
import Link from 'next/link'
import Search from '@/public/components/search/Search'

const Header = () => {
  return (
    <header className={styles['c-header']}>
      <Link href={`/`}>
        <h1>UNORSS</h1>
      </Link>

      <nav className={styles['c-header__nav']}>
        <ul className={styles['c-header__nav__list']}>
          <li className={styles['c-header__nav__list__item']}>
            <Link href={`/bookmark`}>
              ブックマーク
            </Link>
          </li>
          <li className={styles['c-header__nav__list__item']}>
            <Search />
          </li>
        </ul>
      </nav>
    </header>
  )
}

export default Header

フッターの作成

Footer.tsx
import React from 'react'
import styles from './Footer.module.scss'

const Footer = () => {
  return (
    <footer className={styles['c-footer']}>
      Footer
    </footer>
  )
}

export default Footer

サイドバーの作成

Sidebar.tsx
import React from 'react'
import styles from './Sidebar.module.scss'

const Sidebar = () => {
  return (
    <aside className={styles['c-sidebar']}>
      <ul className={styles['c-sidebar__card']}>
        <p className={styles['c-sidebar__card--title']}>配信元別</p>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
      </ul>
      <ul className={styles['c-sidebar__card']}>
        <p className={styles['c-sidebar__card--title']}>カテゴリ別</p>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
      </ul>
      <ul className={styles['c-sidebar__card']}>
        <p className={styles['c-sidebar__card--title']}>月別</p>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
        <li className={styles['c-sidebar__card__item']}>
          <a href="/">リンク</a>
        </li>
      </ul>
    </aside>
  )
}

export default Sidebar

レイアウトへの組み込み

src/app/layout.tsx
import type { Metadata } from "next";
import "@/public/styles/destyle.css"
import "@/public/styles/global.scss"
import Header from "@/public/components/header/Header";
import Footer from "@/public/components/footer/Footer";
import Sidebar from "@/public/components/sidebar/Sidebar";

export const metadata: Metadata = {
  title: "RSS reader",
  description: "ワタシのワタシによるワタシのためのRSSリーダー",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja" id="html">
      <body>
        <Header />
        <div className="l-inner">
          <main>
            {children}
          </main>
          <Sidebar />
        </div>
        <Footer />
      </body>
    </html>
  );
}

結果。
image.png

(リンク等全てダミー状態。)

RSS一覧取得

rss-parceのインストール

$ npm i rss-parser

試しにzennのrssを取得・表示

import Parser from 'rss-parser'

const feed = await new Parser().parseURL("https://zenn.dev/niiyama_k/feed")

console.log(feed.title)

feed.items.map(item => {
console.log(item.title)
console.log(item.link)
})
出力結果
新山 慶介さんのフィード
MAMPによるWordPress開発環境の最大アップロード容量を変更する
https://zenn.dev/niiyama_k/articles/change-max-upload-size-mamp
【EC-CUBE】ECサイトの移転がうまくいかない
https://zenn.dev/niiyama_k/articles/6774e2befe7a67
【Xserver】phpMyAdminのID・パスワード
https://zenn.dev/niiyama_k/articles/c01c402e3f59c6
【React】useLocation で 今いるページ・前いたページのpathを取得する
https://zenn.dev/niiyama_k/articles/eab421dddf2956
Sass * FROCSSによるサイトコーディングのベストプラクティス
https://zenn.dev/niiyama_k/articles/7b041bc8ee577a
Swagger(OpenAPI)によるAPI設計
https://zenn.dev/niiyama_k/articles/3975164ab205df
gridレイアウトでカレンダーのデザイン(html / sass)
https://zenn.dev/niiyama_k/articles/e83f57ad0b880b
[almalinux9 + laravel] sodiumがなくて怒られる
https://zenn.dev/niiyama_k/articles/c3ec0913a4ba61
ChatGPTでシステム開発の設計書テンプレートを作成する
https://zenn.dev/niiyama_k/articles/7aebf968001607

TOPページで一覧取得

src/app/page.tsx
import { client, getList } from '@/libs/client'
import styles from './FrontPage.module.scss'
import Parser from 'rss-parser'

import ArticleCard from '@/public/components/card/ArticleCard'

const FrontPage = async () => {
  const { contents } = await getList();
  const parse = new Parser();

  return (
    <div className='p-front-page'>
      <ul className={styles['p-front-page__list']}>
        {contents.map(async (rss) => {
          const feed = await parse.parseURL(rss.url)
          return feed.items.map((item) => {
            return (
              <ArticleCard
                date={item.pubDate ?? item.date}
                link={item.link}
                title={item.title}
                site_title={rss.title}
              />
            )
          })
        })}
      </ul>

      //後でお気に入りカテゴリのループが入る
    </div>
  )
}

export default FrontPage
{contents.map(async (rss) => {
  const feed = await parse.parseURL(rss.url)
  return feed.items.map((item) => {
    return (
    )
  })
})}

これの理解に結構時間がかかった。

microCMSに登録してあるRSSのURLを取得 → rss-parserでパース → 並び替え

という流れが必要だった。

(個人的にmap関数のネストはあまり良くなさそうですがどうなんでしょう...?教えて有識者様)

各記事のカードはコンポーネントにした。

コンポーネントに渡している情報は以下の通り

  • item.pubDate ?? item.date:日付情報
  • item.link:各記事へのリンク
  • item.title:各記事のタイトル
  • rss.title:microCMSで登録したRSSタイトル

カードコンポーネント

ArticleCard.tsx
import Link from 'next/link'
import React from 'react'
import dayjs from 'dayjs';
import styles from './ArticleCard.module.scss';

type Props = {
  date: string | undefined
  link: string | undefined
  title: string | undefined
  site_title: string
}

function getOrder(millis: number, asc = false) {
  return (asc ? 1 : -1) * Math.floor(millis / 1000); // order は昇順なので、降順に見せたい場合は -1 を掛ける
}

const ArticleCard = (props: Props) => {
  return (
    <li style={{ order: getOrder(dayjs(props.date).valueOf()) }} className={styles['c-article-card']}>
      <Link href={`${props.link}`}>
        <img src="" alt="" className={styles['c-article-card--img']} />
        <time className={styles['c-article-card--time']}>{dayjs(props.date).format('YYYY-MM-DD')}</time>
        <h2 className={styles['c-article-card--title']}>{props.title}</h2>
        <span className={styles['c-article-card--site-title']}>{props.site_title}</span>
      </Link>
    </li>
  )
}

export default ArticleCard

最初の function getOrderは、取得したカードを昇順で並べるために必要な関数。
コピペしてきたので正直よくわかってない。けど動いてるからとりあえずOK。
(難しいことは後で考える派。)

日付順の並び替えやフォーマットに日付や時間を扱うパッケージが必要だったのでインストールした。

$ npm i dayjs

使用用途は次のとおり。

//渡された日付情報をYYYY-MM-DD形式でフォーマットしている
{dayjs(props.date).format('YYYY-MM-DD')}
//渡された日付情報をミリ秒unixtimeに変換(昇順ソートに必要)
(dayjs(props.date).valueOf())

ここまで作ってスタイル適用するとこうなる。

image.png
ちゃんとリンクも繋がってるし、いい感じ。

時間かかったのでちょっと調整して一旦プッシュ。

次回予告

次回はリンク別ページ・カテゴリページの作成を行っていく予定。
一気に進めすぎて疲れたのでちょっぴり更新頻度落ちます。

案件のご依頼・ご相談について

ワタシが運営するUNOTAMEでは、制作案件やPM業務の外部委託案件などを承っております。
少しでも興味のある方はぜひカジュアルにお話しさせていただけますと嬉しいです。

Twitter(X)のDMや下記ポートフォリオサイトからのお問い合わせ、お待ちしております。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?