aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-01-13 18:35:48 +0100
committerArmand Philippot <git@armandphilippot.com>2022-01-13 18:35:48 +0100
commit02ee023272c4f28fd866de40dd2b15a7f7b75a4a (patch)
treee7d29b73e679f07906068593d0e25bf6f8c728fe
parent606461f857e06b06429dd5738be642f9d1b459be (diff)
parent320b5782f348d42f6a2bb74a70d4d114525355e4 (diff)
feat: add feeds (rss, atom, json)
-rw-r--r--.env.example6
-rw-r--r--next.config.js8
-rw-r--r--package.json1
-rw-r--r--src/components/Layouts/Layout.tsx22
-rw-r--r--src/pages/atom.xml.tsx20
-rw-r--r--src/pages/feed.json.tsx20
-rw-r--r--src/pages/feed.xml.tsx20
-rw-r--r--src/utils/helpers/rss.ts61
-rw-r--r--yarn.lock19
9 files changed, 177 insertions, 0 deletions
diff --git a/.env.example b/.env.example
index c4ddb0e..efeb49d 100644
--- a/.env.example
+++ b/.env.example
@@ -1,6 +1,12 @@
+FRONTEND_URL="https://www.frontend.com"
BACKEND_URL="https://www.backend.com"
GRAPHQL_ENDPOINT="/graphql"
+AUTHOR_NAME="Your Name"
+AUTHOR_EMAIL="your@email.com"
+AUTHOR_URL="https://www.yourWebsite.com/"
+FEED_DESCRIPTION="What you want..."
+
NEXT_PUBLIC_GRAPHQL_API="$BACKEND_URL$GRAPHQL_ENDPOINT"
# Use this only in development mode. It prevents "unable to verify the first
diff --git a/next.config.js b/next.config.js
index 25db587..59ca4f4 100644
--- a/next.config.js
+++ b/next.config.js
@@ -14,6 +14,14 @@ const nextConfig = {
},
poweredByHeader: false,
reactStrictMode: true,
+ async rewrites() {
+ return [
+ {
+ source: '/feed',
+ destination: '/feed.xml',
+ },
+ ];
+ },
sassOptions: {
includePaths: [
path.join(__dirname, 'styles'),
diff --git a/package.json b/package.json
index f20aa74..927b3a5 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"@types/mdx": "^2.0.1",
"@types/prismjs": "^1.16.6",
"babel-plugin-prismjs": "^2.1.0",
+ "feed": "^4.2.2",
"graphql": "^16.1.0",
"graphql-request": "^3.7.0",
"modern-normalize": "^1.1.0",
diff --git a/src/components/Layouts/Layout.tsx b/src/components/Layouts/Layout.tsx
index 35e7d27..8a57cf6 100644
--- a/src/components/Layouts/Layout.tsx
+++ b/src/components/Layouts/Layout.tsx
@@ -4,6 +4,8 @@ import Header from '@components/Header/Header';
import Main from '@components/Main/Main';
import Breadcrumb from '@components/Breadcrumb/Breadcrumb';
import { t } from '@lingui/macro';
+import Head from 'next/head';
+import { config } from '@config/website';
const Layout = ({
children,
@@ -14,6 +16,26 @@ const Layout = ({
}) => {
return (
<>
+ <Head>
+ <link
+ rel="alternate"
+ href="/feed.xml"
+ type="application/rss+xml"
+ title={`${config.name}'s RSS feed`}
+ />
+ <link
+ rel="alternate"
+ href="/atom.xml"
+ type="application/atom+xml"
+ title={`${config.name}'s RSS feed`}
+ />
+ <link
+ rel="alternate"
+ href="/feed.json"
+ type="application/feed+json"
+ title={`${config.name}'s RSS feed`}
+ />
+ </Head>
<a href="#main" className="screen-reader-text">{t`Skip to content`}</a>
<Header isHome={isHome} />
<Main>{children}</Main>
diff --git a/src/pages/atom.xml.tsx b/src/pages/atom.xml.tsx
new file mode 100644
index 0000000..e6908bd
--- /dev/null
+++ b/src/pages/atom.xml.tsx
@@ -0,0 +1,20 @@
+import { generateFeed } from '@utils/helpers/rss';
+import { GetServerSideProps } from 'next';
+
+const Feed = () => null;
+
+export const getServerSideProps: GetServerSideProps = async ({ res }) => {
+ const feed = await generateFeed();
+
+ if (res) {
+ res.setHeader('Content-Type', 'text/xml');
+ res.write(`${feed.atom1()}`);
+ res.end();
+ }
+
+ return {
+ props: {},
+ };
+};
+
+export default Feed;
diff --git a/src/pages/feed.json.tsx b/src/pages/feed.json.tsx
new file mode 100644
index 0000000..e113b46
--- /dev/null
+++ b/src/pages/feed.json.tsx
@@ -0,0 +1,20 @@
+import { generateFeed } from '@utils/helpers/rss';
+import { GetServerSideProps } from 'next';
+
+const Feed = () => null;
+
+export const getServerSideProps: GetServerSideProps = async ({ res }) => {
+ const feed = await generateFeed();
+
+ if (res) {
+ res.setHeader('Content-Type', 'application/json');
+ res.write(`${feed.json1()}`);
+ res.end();
+ }
+
+ return {
+ props: {},
+ };
+};
+
+export default Feed;
diff --git a/src/pages/feed.xml.tsx b/src/pages/feed.xml.tsx
new file mode 100644
index 0000000..093cab8
--- /dev/null
+++ b/src/pages/feed.xml.tsx
@@ -0,0 +1,20 @@
+import { generateFeed } from '@utils/helpers/rss';
+import { GetServerSideProps } from 'next';
+
+const Feed = () => null;
+
+export const getServerSideProps: GetServerSideProps = async ({ res }) => {
+ const feed = await generateFeed();
+
+ if (res) {
+ res.setHeader('Content-Type', 'text/xml');
+ res.write(`${feed.rss2()}`);
+ res.end();
+ }
+
+ return {
+ props: {},
+ };
+};
+
+export default Feed;
diff --git a/src/utils/helpers/rss.ts b/src/utils/helpers/rss.ts
new file mode 100644
index 0000000..7851ff8
--- /dev/null
+++ b/src/utils/helpers/rss.ts
@@ -0,0 +1,61 @@
+import { config } from '@config/website';
+import { getPublishedPosts } from '@services/graphql/queries';
+import { ArticlePreview } from '@ts/types/articles';
+import { PostsList } from '@ts/types/blog';
+import { Feed } from 'feed';
+
+const getAllPosts = async (): Promise<ArticlePreview[]> => {
+ const posts: ArticlePreview[] = [];
+ let hasNextPage = true;
+ let after = undefined;
+
+ do {
+ const postsList: PostsList = await getPublishedPosts({ first: 10, after });
+ posts.push(...postsList.posts);
+ hasNextPage = postsList.pageInfo.hasNextPage;
+ after = postsList.pageInfo.endCursor;
+ } while (hasNextPage);
+
+ return posts;
+};
+
+export const generateFeed = async () => {
+ const websiteUrl = process.env.FRONTEND_URL ? process.env.FRONTEND_URL : '';
+ const author = {
+ name: config.name,
+ email: process.env.AUTHOR_EMAIL,
+ link: websiteUrl,
+ };
+ const copyright = `${config.name} CC BY SA ${config.copyright.startYear} - ${config.copyright.endYear}`;
+ const title = `${config.name} | ${config.baseline}`;
+
+ const feed = new Feed({
+ author,
+ copyright,
+ description: process.env.FEED_DESCRIPTION,
+ feedLinks: {
+ json: `${websiteUrl}/feed/json`,
+ atom: `${websiteUrl}/feed/atom`,
+ },
+ generator: 'Feed & NextJS',
+ id: websiteUrl,
+ language: config.defaultLocale,
+ link: websiteUrl,
+ title,
+ });
+
+ const posts = await getAllPosts();
+
+ posts.forEach((post) => {
+ feed.addItem({
+ content: post.intro,
+ date: new Date(post.dates.publication),
+ description: post.intro,
+ id: post.id,
+ link: `${websiteUrl}/article/${post.slug}`,
+ title: post.title,
+ });
+ });
+
+ return feed;
+};
diff --git a/yarn.lock b/yarn.lock
index 6ca5fd1..1c78c56 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4129,6 +4129,13 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
+feed@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e"
+ integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==
+ dependencies:
+ xml-js "^1.6.11"
+
figures@^3.0.0, figures@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -7660,6 +7667,11 @@ sass@^1.45.0:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
+sax@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
+
saxes@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
@@ -8912,6 +8924,13 @@ ws@^7.4.6:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
+xml-js@^1.6.11:
+ version "1.6.11"
+ resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
+ integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
+ dependencies:
+ sax "^1.2.4"
+
xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"