diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
index e73f5db72049..1571085f5c72 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap
@@ -86,6 +86,86 @@ exports[`blogFeed atom shows feed item for each post 1`] = `
"
`;
+exports[`blogFeed json shows feed item for each post 1`] = `
+"{
+ \\"version\\": \\"https://jsonfeed.org/version/1\\",
+ \\"title\\": \\"Hello Blog\\",
+ \\"home_page_url\\": \\"https://docusaurus.io/myBaseUrl/blog\\",
+ \\"description\\": \\"Hello Blog\\",
+ \\"items\\": [
+ {
+ \\"id\\": \\"/mdx-require-blog-post\\",
+ \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/mdx-require-blog-post\\",
+ \\"title\\": \\"MDX Blog Sample with require calls\\",
+ \\"summary\\": \\"Test MDX with require calls\\",
+ \\"date_modified\\": \\"2021-03-06T00:00:00.000Z\\"
+ },
+ {
+ \\"id\\": \\"/mdx-blog-post\\",
+ \\"content_html\\": \\"
HTML Heading 1
HTML Heading 2
HTML Paragraph
Import DOM
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Normal Text Italics Text Bold Text
link\\\\n
\\",
+ \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/mdx-blog-post\\",
+ \\"title\\": \\"Full Blog Sample\\",
+ \\"summary\\": \\"HTML Heading 1\\",
+ \\"date_modified\\": \\"2021-03-05T00:00:00.000Z\\"
+ },
+ {
+ \\"id\\": \\"/hey/my super path/héllô\\",
+ \\"content_html\\": \\"complex url slug
\\",
+ \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/hey/my super path/héllô\\",
+ \\"title\\": \\"Complex Slug\\",
+ \\"summary\\": \\"complex url slug\\",
+ \\"date_modified\\": \\"2020-08-16T00:00:00.000Z\\"
+ },
+ {
+ \\"id\\": \\"/simple/slug\\",
+ \\"content_html\\": \\"simple url slug
\\",
+ \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/simple/slug\\",
+ \\"title\\": \\"Simple Slug\\",
+ \\"summary\\": \\"simple url slug\\",
+ \\"date_modified\\": \\"2020-08-15T00:00:00.000Z\\",
+ \\"author\\": {
+ \\"name\\": \\"Sébastien Lorber\\",
+ \\"url\\": \\"https://sebastienlorber.com\\"
+ }
+ },
+ {
+ \\"id\\": \\"/draft\\",
+ \\"content_html\\": \\"this post should not be published yet
\\",
+ \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/draft\\",
+ \\"title\\": \\"draft\\",
+ \\"summary\\": \\"this post should not be published yet\\",
+ \\"date_modified\\": \\"2020-02-27T00:00:00.000Z\\"
+ },
+ {
+ \\"id\\": \\"/heading-as-title\\",
+ \\"content_html\\": \\"\\",
+ \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/heading-as-title\\",
+ \\"title\\": \\"some heading\\",
+ \\"date_modified\\": \\"2019-01-02T00:00:00.000Z\\"
+ },
+ {
+ \\"id\\": \\"/date-matter\\",
+ \\"content_html\\": \\"date inside front matter
\\",
+ \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/date-matter\\",
+ \\"title\\": \\"date-matter\\",
+ \\"summary\\": \\"date inside front matter\\",
+ \\"date_modified\\": \\"2019-01-01T00:00:00.000Z\\"
+ },
+ {
+ \\"id\\": \\"/2018/12/14/Happy-First-Birthday-Slash\\",
+ \\"content_html\\": \\"Happy birthday! (translated)
\\",
+ \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/2018/12/14/Happy-First-Birthday-Slash\\",
+ \\"title\\": \\"Happy 1st Birthday Slash! (translated)\\",
+ \\"summary\\": \\"Happy birthday! (translated)\\",
+ \\"date_modified\\": \\"2018-12-14T00:00:00.000Z\\",
+ \\"author\\": {
+ \\"name\\": \\"Yangshun Tay (translated)\\"
+ }
+ }
+ ]
+}"
+`;
+
exports[`blogFeed rss shows feed item for each post 1`] = `
"
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
index 2c2d0337a46e..6493aa1bf734 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts
@@ -50,7 +50,7 @@ async function testGenerateFeeds(
}
describe('blogFeed', () => {
- (['atom', 'rss'] as const).forEach((feedType) => {
+ (['atom', 'rss', 'json'] as const).forEach((feedType) => {
describe(`${feedType}`, () => {
test('should not show feed without posts', async () => {
const siteDir = __dirname;
@@ -117,8 +117,22 @@ describe('blogFeed', () => {
defaultReadingTime({content}),
} as PluginOptions,
);
- const feedContent =
- feed && (feedType === 'rss' ? feed.rss2() : feed.atom1());
+
+ let feedContent = '';
+ switch (feedType) {
+ case 'rss':
+ feedContent = feed.rss2();
+ break;
+ case 'json':
+ feedContent = feed.json1();
+ break;
+ case 'atom':
+ feedContent = feed.atom1();
+ break;
+ default:
+ break;
+ }
+
expect(feedContent).toMatchSnapshot();
});
});
diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts
index 78663ce02238..4c0fdef73f16 100644
--- a/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts
+++ b/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts
@@ -78,7 +78,7 @@ test('should convert all feed type to array with other feed type', () => {
});
expect(value).toEqual({
...DEFAULT_OPTIONS,
- feedOptions: {type: ['rss', 'atom'], copyright: ''},
+ feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''},
});
});
diff --git a/packages/docusaurus-plugin-content-blog/src/feed.ts b/packages/docusaurus-plugin-content-blog/src/feed.ts
index b438fcecd1b6..659141b67979 100644
--- a/packages/docusaurus-plugin-content-blog/src/feed.ts
+++ b/packages/docusaurus-plugin-content-blog/src/feed.ts
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import {Feed, Author as FeedAuthor} from 'feed';
+import {Feed, Author as FeedAuthor, Item as FeedItem} from 'feed';
import {PluginOptions, Author, BlogPost, FeedType} from './types';
import {normalizeUrl, mdxToHtml} from '@docusaurus/utils';
import {DocusaurusConfig} from '@docusaurus/types';
@@ -68,15 +68,24 @@ export async function generateBlogFeed({
id,
metadata: {title: metadataTitle, permalink, date, description, authors},
} = post;
- feed.addItem({
+
+ const feedItem: FeedItem = {
title: metadataTitle,
id,
link: normalizeUrl([siteUrl, permalink]),
date,
description,
content: mdxToFeedContent(post.content),
- author: authors.map(toFeedAuthor),
- });
+ };
+
+ // json1() method takes the first item of authors array
+ // it causes an error when authors array is empty
+ const feedItemAuthors = authors.map(toFeedAuthor);
+ if (feedItemAuthors.length > 0) {
+ feedItem.author = feedItemAuthors;
+ }
+
+ feed.addItem(feedItem);
});
return feed;
@@ -85,15 +94,26 @@ export async function generateBlogFeed({
async function createBlogFeedFile({
feed,
feedType,
- filePath,
+ generatePath,
}: {
feed: Feed;
feedType: FeedType;
- filePath: string;
+ generatePath: string;
}) {
- const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
+ const [feedContent, feedPath] = (() => {
+ switch (feedType) {
+ case 'rss':
+ return [feed.rss2(), 'rss.xml'];
+ case 'json':
+ return [feed.json1(), 'feed.json'];
+ case 'atom':
+ return [feed.atom1(), 'atom.xml'];
+ default:
+ throw new Error(`Feed type ${feedType} not supported.`);
+ }
+ })();
try {
- await fs.outputFile(filePath, feedContent);
+ await fs.outputFile(path.join(generatePath, feedPath), feedContent);
} catch (err) {
throw new Error(`Generating ${feedType} feed failed: ${err}.`);
}
@@ -118,12 +138,12 @@ export async function createBlogFeedFiles({
}
await Promise.all(
- feedTypes.map(async (feedType) => {
- await createBlogFeedFile({
+ feedTypes.map((feedType) =>
+ createBlogFeedFile({
feed,
feedType,
- filePath: path.join(outDir, options.routeBasePath, `${feedType}.xml`),
- });
- }),
+ generatePath: path.join(outDir, options.routeBasePath),
+ }),
+ ),
);
}
diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts
index 723b048bda3a..9b9e98fb870e 100644
--- a/packages/docusaurus-plugin-content-blog/src/index.ts
+++ b/packages/docusaurus-plugin-content-blog/src/index.ts
@@ -550,6 +550,11 @@ export default function pluginContentBlog(
path: 'atom.xml',
title: `${feedTitle} Atom Feed`,
},
+ json: {
+ type: 'application/json',
+ path: 'feed.json',
+ title: `${feedTitle} JSON Feed`,
+ },
};
const headTags: HtmlTags = [];
diff --git a/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts
index e231ea36bf2a..ff60adc9c94e 100644
--- a/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts
+++ b/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts
@@ -90,12 +90,12 @@ export const PluginOptionSchema = Joi.object({
feedOptions: Joi.object({
type: Joi.alternatives()
.try(
- Joi.array().items(Joi.string()),
+ Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
Joi.alternatives().conditional(
- Joi.string().equal('all', 'rss', 'atom'),
+ Joi.string().equal('all', 'rss', 'atom', 'json'),
{
then: Joi.custom((val) =>
- val === 'all' ? ['rss', 'atom'] : [val],
+ val === 'all' ? ['rss', 'atom', 'json'] : [val],
),
},
),
diff --git a/packages/docusaurus-plugin-content-blog/src/types.ts b/packages/docusaurus-plugin-content-blog/src/types.ts
index 15d129bcca67..76fbacc95980 100644
--- a/packages/docusaurus-plugin-content-blog/src/types.ts
+++ b/packages/docusaurus-plugin-content-blog/src/types.ts
@@ -24,7 +24,7 @@ export interface BlogContent {
blogTagsListPath: string | null;
}
-export type FeedType = 'rss' | 'atom';
+export type FeedType = 'rss' | 'atom' | 'json';
export type FeedOptions = {
type?: FeedType[] | null;
diff --git a/website/docs/api/plugins/plugin-content-blog.md b/website/docs/api/plugins/plugin-content-blog.md
index 06d7bdca5b5f..1d325ce3c198 100644
--- a/website/docs/api/plugins/plugin-content-blog.md
+++ b/website/docs/api/plugins/plugin-content-blog.md
@@ -56,8 +56,8 @@ Accepted fields:
| `showReadingTime` | `boolean` | `true` | Show estimated reading time for the blog post. |
| `readingTime` | `ReadingTimeFunctionOption` | The default reading time | A callback to customize the reading time number displayed. |
| `authorsMapPath` | `string` | `'authors.yml'` | Path to the authors map file, relative to the blog content directory specified with `path`. Can also be a `json` file. |
-| `feedOptions` | _See below_ | `{type: ['rss', 'atom']}` | Blog feed. If undefined, no rss feed will be generated. |
-| `feedOptions.type` | 'rss' \| 'atom' \| 'all'
(or array of multiple options) | **Required** | Type of feed to be generated. |
+| `feedOptions` | _See below_ | `{type: ['rss', 'atom']}` | Blog feed. |
+| `feedOptions.type` | FeedType \| FeedType[] \| 'all' \| null
| **Required** | Type of feed to be generated. Use `null` to disable generation. |
| `feedOptions.title` | `string` | `siteConfig.title` | Title of the feed. |
| `feedOptions.description` | `string` | \`${siteConfig.title} Blog\`
| Description of the feed. |
| `feedOptions.copyright` | `string` | `undefined` | Copyright message. |
@@ -90,6 +90,8 @@ type ReadingTimeFunctionOption = (params: {
frontMatter: BlogPostFrontMatter & Record;
defaultReadingTime: ReadingTimeFunction;
}) => number | undefined;
+
+type FeedType = 'rss' | 'atom' | 'json';
```
## Example configuration {#ex-config}
diff --git a/website/docs/blog.mdx b/website/docs/blog.mdx
index 171cc12c7607..035732b7ede9 100644
--- a/website/docs/blog.mdx
+++ b/website/docs/blog.mdx
@@ -455,12 +455,14 @@ module.exports = {
## Feed {#feed}
-You can generate RSS/Atom feed by passing feedOptions. By default, RSS and Atom feeds are generated. To disable feed generation, set `feedOptions.type` to `null`.
+You can generate RSS / Atom / JSON feed by passing `feedOptions`. By default, RSS and Atom feeds are generated. To disable feed generation, set `feedOptions.type` to `null`.
```ts
+type FeedType = 'rss' | 'atom' | 'json';
+
type BlogOptions = {
feedOptions?: {
- type?: 'rss' | 'atom' | 'all' | null;
+ type?: FeedType | 'all' | FeedType[] | null;
title?: string;
description?: string;
copyright: string;
@@ -490,20 +492,32 @@ module.exports = {
};
```
-Accessing the feed:
+The feeds can be found at:
-The feed for RSS can be found at:
+
+
```text
-https://{your-domain}/blog/rss.xml
+https://example.com/blog/rss.xml
```
-and for Atom:
+
+
```text
-https://{your-domain}/blog/atom.xml
+https://example.com/blog/atom.xml
```
+
+
+
+```text
+https://example.com/blog/feed.json
+```
+
+
+
+
## Advanced topics {#advanced-topics}
### Blog-only mode {#blog-only-mode}