Skip to content

Commit

Permalink
feat: workaround for next-sitemap bug, blog posts SSG
Browse files Browse the repository at this point in the history
  • Loading branch information
TheCatLady committed Oct 12, 2023
1 parent b967594 commit fba1852
Show file tree
Hide file tree
Showing 16 changed files with 250 additions and 172 deletions.
18 changes: 18 additions & 0 deletions next-sitemap.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,29 @@
module.exports = {
siteUrl: 'https://fix.tt',
generateRobotsTxt: true,
exclude: ['/server-sitemap-index.xml'],
additionalPaths: async (config) =>
await Promise.all(
['/', '/blog', '/code-of-conduct', '/cookie-policy'].map(
async (path) => await config.transform(config, path),
),
),
robotsTxtOptions: {
additionalSitemaps: ['https://fix.tt/server-sitemap-index.xml'],
policies: [
process.env.VERCEL_ENV === 'production'
? { userAgent: '*', allow: '/' }
: { userAgent: '*', disallow: '/' },
{
userAgent: '*',
disallow: [
'/*.json$',
'/*_buildManifest.js$',
'/*_middlewareManifest.js$',
'/*_ssgManifest.js$',
'/*.js$',
],
},
],
},
};
134 changes: 134 additions & 0 deletions src/api/hashnode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { gql, request } from 'graphql-request';

import { HASHNODE_ENDPOINT, HASHNODE_HOST } from '@/constants/hashnode';
import {
HashnodePostResponse,
HashnodePostsResponse,
} from '@/interfaces/hashnode';

export async function getHashnodePostSlugs() {
const variables = {
host: HASHNODE_HOST,
first: 20,
};

const query = gql`
query Publication($host: String!, $first: Int!) {
publication(host: $host) {
posts(first: $first) {
edges {
node {
slug
}
}
}
}
}
`;

const data = await request<HashnodePostsResponse>(
HASHNODE_ENDPOINT,
query,
variables,
);

return data.publication.posts.edges.map((edge) => edge.node.slug);
}

export async function getHashnodePosts({
first,
after,
}: {
first?: number;
after?: string;
}) {
const variables = {
host: HASHNODE_HOST,
first: first && first > 0 ? (first > 20 ? 20 : first) : 5,
after,
};

const query = gql`
query Publication($host: String!, $first: Int!, $after: String) {
publication(host: $host) {
posts(first: $first, after: $after) {
edges {
node {
title
brief
slug
coverImage {
url
}
author {
name
tagline
profilePicture
socialMediaLinks {
website
linkedin
}
}
readTimeInMinutes
publishedAt
}
cursor
}
}
}
}
`;

const data = await request<HashnodePostsResponse>(
HASHNODE_ENDPOINT,
query,
variables,
);

return data.publication.posts.edges;
}

export async function getHashnodePost({ slug }: { slug: string }) {
const variables = {
host: HASHNODE_HOST,
slug,
};

const query = `
query Publication($host: String!, $slug: String!) {
publication(host: $host) {
post(slug: $slug) {
title
subtitle
brief
slug
coverImage {
url
}
author {
name
tagline
profilePicture
socialMediaLinks {
website
linkedin
}
}
content {
markdown
}
readTimeInMinutes
publishedAt
}
}
}
`;

const data = await request<HashnodePostResponse>(
HASHNODE_ENDPOINT,
query,
variables,
);

return data.publication.post;
}
50 changes: 4 additions & 46 deletions src/app/api/blog/post/route.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,10 @@
import { request } from 'graphql-request';
import { type NextRequest, NextResponse } from 'next/server';

import { HASHNODE_ENDPOINT, HASHNODE_HOST } from '@/constants/hashnode';
import { HashnodePostResponse } from '@/interfaces/hashnode';

export const revalidate = 3600; // revalidate at most every hour
import { getHashnodePost } from '@/api/hashnode';

export async function GET(req: NextRequest) {
const variables = {
host: HASHNODE_HOST,
slug: req.nextUrl.searchParams.get('slug'),
};

const query = `
query Publication($host: String!, $slug: String!) {
publication(host: $host) {
post(slug: $slug) {
title
subtitle
brief
slug
coverImage {
url
}
author {
name
tagline
profilePicture
socialMediaLinks {
website
linkedin
}
}
content {
markdown
}
readTimeInMinutes
publishedAt
}
}
}
`;

const data = await request<HashnodePostResponse>(
HASHNODE_ENDPOINT,
query,
variables,
return NextResponse.json(
await getHashnodePost({ slug: req.nextUrl.searchParams.get('slug') ?? '' }),
{ status: 200 },
);

return NextResponse.json(data.publication.post, { status: 200 });
}
59 changes: 9 additions & 50 deletions src/app/api/blog/posts/route.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,15 @@
import { gql, request } from 'graphql-request';
import { type NextRequest, NextResponse } from 'next/server';

import { HASHNODE_ENDPOINT, HASHNODE_HOST } from '@/constants/hashnode';
import { HashnodePostsResponse } from '@/interfaces/hashnode';

export const revalidate = 600; // revalidate at most every 10 minutes
import { getHashnodePosts } from '@/api/hashnode';

export async function GET(req: NextRequest) {
const variables = {
host: HASHNODE_HOST,
first: parseInt(req.nextUrl.searchParams.get('first') ?? '5'),
after: req.nextUrl.searchParams.get('after'),
};

const query = gql`
query Publication($host: String!, $first: Int!, $after: String) {
publication(host: $host) {
posts(first: $first, after: $after) {
edges {
node {
title
brief
slug
coverImage {
url
}
author {
name
tagline
profilePicture
socialMediaLinks {
website
linkedin
}
}
readTimeInMinutes
publishedAt
}
cursor
}
}
}
}
`;

const data = await request<HashnodePostsResponse>(
HASHNODE_ENDPOINT,
query,
variables,
return NextResponse.json(
await getHashnodePosts({
first: parseInt(req.nextUrl.searchParams.get('first') ?? '5'),
after: req.nextUrl.searchParams.get('after') ?? undefined,
}),
{
status: 200,
},
);

return NextResponse.json(data.publication.posts.edges, {
status: 200,
});
}
56 changes: 32 additions & 24 deletions src/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,68 @@
import type { Metadata } from 'next';
import { headers } from 'next/headers';

import BlogPost from '@/components/blog/BlogPost';

import { getHashnodePost, getHashnodePostSlugs } from '@/api/hashnode';
import { siteConfig } from '@/constants/config';
import { isVercel } from '@/constants/env';
import { HashnodePost } from '@/interfaces/hashnode';
import { openGraph } from '@/utils/og';

export const revalidate = 600; // revalidate at most every 10 minutes
export async function generateStaticParams() {
const slugs = await getHashnodePostSlugs();

return slugs.map((slug) => ({
slug,
}));
}

async function getPost(slug: string) {
return await getHashnodePost({ slug });
}

export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const host = headers().get('host');

const blogPost = (await fetch(
`${isVercel ? 'https' : 'http'}://${host}/api/blog/post?slug=${
params.slug
}`,
).then((res) => res.json())) as HashnodePost;
const post = await getPost(params.slug);

return {
title: blogPost.title,
description: blogPost.brief,
title: post.title,
description: post.brief,
openGraph: {
url: `${siteConfig.url}/blog/${params.slug}`,
title: blogPost.title,
description: blogPost.brief,
url: `${siteConfig.url}/blog/${post.slug}`,
title: post.title,
description: post.brief,
images: [
openGraph({
title: blogPost.title,
metadata: blogPost.subtitle,
title: post.title,
metadata: post.subtitle,
}),
],
type: 'article',
},
twitter: {
title: `${blogPost.title} | ${siteConfig.title}`,
description: blogPost.brief,
title: `${post.title} | ${siteConfig.title}`,
description: post.brief,
images: [
openGraph({
title: blogPost.title,
metadata: blogPost.subtitle,
title: post.title,
metadata: post.subtitle,
}),
],
},
};
}

export default function BlogPostPage({ params }: { params: { slug: string } }) {
export default async function BlogPostPage({
params,
}: {
params: { slug: string };
}) {
const post = await getPost(params.slug);

return (
<div className="px-6 py-32 lg:px-8">
<BlogPost slug={params.slug} />
<BlogPost post={post} />
</div>
);
}
4 changes: 1 addition & 3 deletions src/app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import BlogPostList from '@/components/blog/BlogPostList';
import { siteConfig } from '@/constants/config';
import { openGraph } from '@/utils/og';

export const revalidate = 600; // revalidate at most every 10 minutes

export const metadata: Metadata = {
title: 'Blog',
openGraph: {
Expand All @@ -30,7 +28,7 @@ export const metadata: Metadata = {
},
};

export default function BlogPage() {
export default function Blog() {
return (
<div className="py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
Expand Down
Loading

0 comments on commit fba1852

Please sign in to comment.