diff --git a/lib/$path.ts b/lib/$path.ts index 97810d5..233d29f 100644 --- a/lib/$path.ts +++ b/lib/$path.ts @@ -15,6 +15,9 @@ export const pagesPath = { } }) }, + feed_xml: { + $url: (url?: { hash?: string }) => ({ pathname: '/feed.xml' as const, hash: url?.hash }) + }, page: { _pageNumber: (pageNumber: string | number) => ({ $url: (url?: { hash?: string }) => ({ pathname: '/page/[pageNumber]' as const, query: { pageNumber }, hash: url?.hash }) diff --git a/package.json b/package.json index 5c20507..ede989f 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "react-dom": "^17.0.1", "react-scroll": "^1.8.2", "react-use": "^17.2.4", + "rss": "^1.2.2", "swr": "^0.5.6" }, "devDependencies": { @@ -50,6 +51,7 @@ "@types/node": "^14.14.37", "@types/react": "^17.0.3", "@types/react-scroll": "^1.8.2", + "@types/rss": "^0.0.28", "@typescript-eslint/eslint-plugin": "^4.20.0", "@typescript-eslint/parser": "^4.20.0", "babel-jest": "^26.6.3", diff --git a/pages/feed.xml.tsx b/pages/feed.xml.tsx new file mode 100644 index 0000000..a32d3b4 --- /dev/null +++ b/pages/feed.xml.tsx @@ -0,0 +1,20 @@ +import { GetServerSideProps } from 'next' + +import generateFeedXml from '~/scripts/generate-rss-feed' + +export const getServerSideProps: GetServerSideProps = async ({ res }) => { + const feed = await generateFeedXml() + + res.statusCode = 200 + res.setHeader('Cache-Control', 's-maxage=86400, stale-while-revalidate') // 24時間のキャッシュ + res.setHeader('Content-Type', 'text/xml') + res.end(feed) + + return { + props: {}, + } +} + +const FeedPage = () => null + +export default FeedPage diff --git a/scripts/generate-rss-feed.ts b/scripts/generate-rss-feed.ts new file mode 100644 index 0000000..c1fa924 --- /dev/null +++ b/scripts/generate-rss-feed.ts @@ -0,0 +1,28 @@ +import RSS from 'rss' + +import { getContents } from '~/src/utils/getContents' +import { description, returnTitle, SITE_URL } from '~/src/utils/meta' + +const generateFeedXml = async () => { + const feed = new RSS({ + title: returnTitle(), + description, + feed_url: `${SITE_URL}/feed.xml`, + site_url: SITE_URL, + language: 'ja', + }) + + const { contents } = await getContents() + contents.forEach((content) => { + feed.item({ + title: returnTitle(content.title), + description: content.description, + date: content.publishedAt ?? content.createdAt, + url: `${SITE_URL}/${content.id}`, + }) + }) + + return feed.xml() +} + +export default generateFeedXml diff --git a/scripts/generate-sitemap.ts b/scripts/generate-sitemap.ts index 9eafbc7..de7be26 100644 --- a/scripts/generate-sitemap.ts +++ b/scripts/generate-sitemap.ts @@ -15,6 +15,7 @@ const generateSitemap = async () => { '!pages/_*.tsx', '!pages/**/[*.tsx', '!pages/sitemap.xml.tsx', + '!pages/feed.xml.tsx', '!pages/404.tsx', '!pages/api', ]) diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 2eb9245..4b63eeb 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -4,7 +4,7 @@ import React from 'react' import { Banner as ApiBanner } from '~/src/types/microCMS/api/Banner' import { Blog } from '~/src/types/microCMS/api/Blog' import { Category } from '~/src/types/microCMS/api/Category' -import { DESCRIPTION, SITE_URL, OG_DESCRIPTION, OG_IMAGE, OG_TYPE } from '~/src/utils/meta' +import { DESCRIPTION, SITE_URL, OG_DESCRIPTION, OG_IMAGE, OG_TYPE, description } from '~/src/utils/meta' import { Banner } from '../Banner' import { Categories } from '../Categories' @@ -27,8 +27,6 @@ export type { ContainerProps as LayoutProps } type Props = ContainerProps -const description = '' - const Component: React.FC = ({ banner, categories, children, popularArticles, latestArticles }) => ( <> diff --git a/src/components/Share/Share.tsx b/src/components/Share/Share.tsx index d6c65ad..ed4a493 100644 --- a/src/components/Share/Share.tsx +++ b/src/components/Share/Share.tsx @@ -49,11 +49,11 @@ const Container: React.VFC = ({ id, title }) => { src: `${process.env.NEXT_PUBLIC_BASE_PATH}/images/icon_hatena.svg`, alt: 'はてなブックマーク', }, - // { - // href: `${SITE_URL}/feed.xml`, - // src: `${process.env.NEXT_PUBLIC_BASE_PATH}/images/icon_feed.svg`, - // alt: 'フィード', - // }, + { + href: `${SITE_URL}/feed.xml`, + src: `${process.env.NEXT_PUBLIC_BASE_PATH}/images/icon_feed.svg`, + alt: 'フィード', + }, ], [id, title] ) diff --git a/src/utils/meta.ts b/src/utils/meta.ts index 59eb944..94ad32e 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -11,6 +11,7 @@ if (process.env.NEXT_PUBLIC_SITE_URL === undefined) { export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL const title = 'microCMSブログ' +export const description = '' export const returnTitle = (pageTitle?: string) => { if (pageTitle !== undefined) { diff --git a/src/utils/microCMSHeaders.ts b/src/utils/microCMSHeaders.ts index 325ccff..8c04cf2 100644 --- a/src/utils/microCMSHeaders.ts +++ b/src/utils/microCMSHeaders.ts @@ -1,9 +1,5 @@ import { MicroCMSReqHeaders } from '../types/microCMS/Headers' -if (process.env.API_KEY === undefined) { - throw Error('envファイルにAPI_KEYを設定してください。') -} - export const headers: MicroCMSReqHeaders = { - 'X-API-KEY': process.env.API_KEY, + 'X-API-KEY': process.env.API_KEY ?? '', } diff --git a/yarn.lock b/yarn.lock index 86fd280..83f07b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1056,6 +1056,11 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/rss@^0.0.28": + version "0.0.28" + resolved "https://registry.yarnpkg.com/@types/rss/-/rss-0.0.28.tgz#2780ffec484ca3c69cce63e314efd4ca4454f6c9" + integrity sha512-Kymd0BI1tBOSwOwCN5Y8dtlJegTIF+izS8tJETiivJ7qAJGHhnuZjiunXWqBkgv6dg0ZZWyf0kEfMgkr4YWJzg== + "@types/scheduler@*": version "0.16.1" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" @@ -6051,6 +6056,18 @@ mime-db@1.46.0, mime-db@^1.28.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== +mime-db@~1.25.0: + version "1.25.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.25.0.tgz#c18dbd7c73a5dbf6f44a024dc0d165a1e7b1c392" + integrity sha1-wY29fHOl2/b0SgJNwNFloeexw5I= + +mime-types@2.1.13: + version "2.1.13" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.13.tgz#e07aaa9c6c6b9a7ca3012c69003ad25a39e92a88" + integrity sha1-4HqqnGxrmnyjASxpADrSWjnpKog= + dependencies: + mime-db "~1.25.0" + mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.29" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" @@ -7791,6 +7808,14 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rss@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/rss/-/rss-1.2.2.tgz#50a1698876138133a74f9a05d2bdc8db8d27a921" + integrity sha1-UKFpiHYTgTOnT5oF0r3I240nqSE= + dependencies: + mime-types "2.1.13" + xml "1.0.1" + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -9485,6 +9510,11 @@ xml2js@^0.4.5: sax ">=0.6.0" xmlbuilder "~11.0.0" +xml@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"