Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: add sitemaps and SEO related headers #239

Merged
merged 8 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

# production
/build
public/robots.txt
public/sitemap*

# misc
.DS_Store
Expand Down
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
engine-strict=true
enable-pre-post-scripts=true
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"faustwp",
"heroicons",
"Kevinbatdorf",
"lastmod",
"recma",
"rehype",
"shiki",
Expand Down
22 changes: 22 additions & 0 deletions next-sitemap.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { env } from "node:process";
import { URL } from "node:url";

const SITE_URL = env.NEXT_PUBLIC_SITE_URL;

export default {
siteUrl: SITE_URL,
generateRobotsTxt: true,
exclude: ["/wp-sitemap.xml"], // <= exclude here

robotsTxtOptions: {
additionalSitemaps: [
new URL("/wp-sitemap.xml", SITE_URL), // <==== Add here
],
policies: [
{
userAgent: "*",
disallow: "/",
},
],
},
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"scripts": {
"dev": "faust dev",
"build": "faust build",
"postbuild": "next-sitemap --config next-sitemap.config.mjs",
"generate": "faust generatePossibleTypes",
"stylesheet": "faust generateGlobalStylesheet",
"start": "faust start",
Expand Down Expand Up @@ -33,7 +34,7 @@
"html-to-text": "^9.0.5",
"http-status-codes": "^2.3.0",
"lodash.debounce": "^4.0.8",
"next": "^15.1.1",
"next-sitemap": "^4.2.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"recma-nextjs-static-props": "^2.0.1",
Expand Down
31 changes: 28 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion possibleTypes.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/components/docs-layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
DisclosurePanel,
} from "@headlessui/react";
import { ChevronRightIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/router";
import DocsBreadcrumbs from "./docs-breadcrumbs";
import DocsPreviousNextLinks from "./docs-previous-next-link";
import OnThisPageNav from "./on-this-page-nav";
import DocsNav from "@/components/docs-nav";
import Seo from "@/components/seo";
import routes from "@/pages/docs/nav.json";
import "rehype-callouts/theme/vitepress";

Expand All @@ -31,8 +33,11 @@ const flattenRoutes = (routeConfig) => {

export default function DocumentPage({ children, metadata }) {
const flatRoutes = flattenRoutes(routes);
const { asPath } = useRouter();

return (
<>
<Seo title={metadata.title} url={asPath} />
<Disclosure
as="div"
className="sticky top-[84px] z-10 border-b-[1px] border-gray-800 bg-gray-900/80 backdrop-blur-sm md:hidden"
Expand Down
6 changes: 1 addition & 5 deletions src/components/seo.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import Head from "next/head";

export default function SEO({ title, description, imageUrl, url }) {
if (!title && !description && !imageUrl && !url) {
return;
}

return (
<Head>
<meta content="website" property="og:type" />
<meta content="summary_large_image" property="twitter:card" />

{title && (
<>
<title>{title}</title>
<title>{`${title} | Faust.js`}</title>
<meta content={title} name="title" />
<meta content={title} property="og:title" />
<meta content={title} property="twitter:title" />
Expand Down
10 changes: 9 additions & 1 deletion src/pages/blog/[slug].jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { gql, useQuery } from "@apollo/client";
import { WordPressBlocksViewer } from "@faustwp/blocks";
import { flatListToHierarchical, getNextStaticProps } from "@faustwp/core";
import Seo from "@/components/seo";
import blocks from "@/wp-blocks";

export default function SinglePost(properties) {
Expand All @@ -15,13 +16,18 @@ export default function SinglePost(properties) {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error! {error.message}</p>;

const { title, date, author, editorBlocks } = post;
const { title, date, author, uri, excerpt, editorBlocks } = post;
const blockList = flatListToHierarchical(editorBlocks, {
childrenKey: "innerBlocks",
});

return (
<div className="container mx-auto px-4 py-8">
<Seo
title={title}
url={uri}
description={excerpt.replaceAll(/<\/?\S+>/gm, "")}
/>
<h1 className="mb-4 text-3xl font-bold">{title}</h1>
<p className="mb-8 text-white">
{author.node.name} &middot;{" "}
Expand All @@ -43,6 +49,8 @@ SinglePost.query = gql`
post(id: $slug, idType: SLUG) {
title
date
uri
excerpt
author {
node {
name
Expand Down
6 changes: 6 additions & 0 deletions src/pages/blog/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Link from "next/link";
import { useState, useEffect } from "react";
import Card from "@/components/card";
import Date from "@/components/date";
import Seo from "@/components/seo";

const GET_POSTS = gql`
query getPosts($first: Int!, $after: String) {
Expand Down Expand Up @@ -83,6 +84,11 @@ export default function BlogIndex() {

return (
<main className="container-main container-max container prose prose-invert px-8 py-14 prose-h2:mt-0 prose-h2:text-lg lg:px-16 lg:py-24">
<Seo
title="News"
description="Faust.js blog feed with the latest news."
url="/blog/"
/>
<h1 className="bg-gradient-to-tr from-blue-200 to-teal-300 bg-clip-text text-transparent">
Faust.js news
</h1>
Expand Down
7 changes: 6 additions & 1 deletion src/pages/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import {
} from "@heroicons/react/24/outline";
import Card from "@/components/card";
import Link from "@/components/link";
import Seo from "@/components/seo";

// The Component is required
export default function Index() {
return (
<main>
<Seo
title="Home"
description="The Next.js Headless Toolkit for WordPress."
url="/"
/>
<section className="bg-100-full bg-default-pos bg-hero-gradient">
<div className="container-main container-max container prose prose-invert mt-0 md:prose-xl lg:prose-2xl prose-h1:mb-2 prose-h1:font-bold prose-h1:leading-tight">
<div className="mx-auto max-w-xl px-8 py-24 text-center md:max-w-2xl md:py-36 lg:max-w-3xl">
Expand Down
6 changes: 6 additions & 0 deletions src/pages/showcase/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
import Image from "next/image";
import Link from "@/components/link";
import Seo from "@/components/seo";

const showcases = [
{
Expand Down Expand Up @@ -28,6 +29,11 @@ const showcases = [
export default function Showcase() {
return (
<main className="container-main container-max container prose prose-invert px-8 py-14 md:prose-lg lg:prose-xl prose-h1:mb-2 prose-h1:font-semibold prose-h1:leading-tight prose-img:my-0 lg:px-16 lg:py-24">
<Seo
title="Showcase"
description="Faust.js showcase with sites powered by Faust."
url="/showcase/"
/>
<h1 className="bg-gradient-to-br from-white/80 to-gray-300 bg-clip-text text-center text-transparent">
Faust.js™ Showcase
</h1>
Expand Down
61 changes: 61 additions & 0 deletions src/pages/wp-sitemap.xml/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { env } from "node:process";
import { URL } from "node:url";
import { gql } from "@apollo/client";
import { getApolloClient } from "@faustwp/core";
import { getServerSideSitemapLegacy } from "next-sitemap";

export const getServerSideProps = async (ctx) => {
const client = getApolloClient();

async function getPaginatedQuery(query, previousPosts = []) {
console.log("current post count:", previousPosts.length);
const res = await client.query(query);

const newPosts = [...previousPosts, ...res.data.posts.nodes];
if (res.data.posts.pageInfo.hasNextPage) {
console.log("fetching more posts");
return getPaginatedQuery(
{
query: query.query,
variables: {
after: res.data.posts.pageInfo.endCursor,
first: query.variables.first,
},
},
newPosts,
);
}

return newPosts;
}

const posts = await getPaginatedQuery({
query: gql`
query getPosts($first: Int!, $after: String) {
posts(first: $first, after: $after) {
nodes {
uri
modified
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: { first: 10 },
});

console.log("Final Post Count:", posts.length);

const fields = posts.map((post) => ({
loc: new URL(post.uri, env.NEXT_PUBLIC_SITE_URL).href, // Absolute url
lastmod: post.modified,
}));

return getServerSideSitemapLegacy(ctx, fields);
};

// Default export to prevent next.js errors
export default function Sitemap() {}
Loading