Skip to content

Commit

Permalink
feat(saas): Improved SEO, Metadata to pages (#30)
Browse files Browse the repository at this point in the history
### TL;DR

Implemented page configurations and metadata for various pages in the SaaS application. Added dynamic metadata generation based on page content.

### What changed?

- Added page configurations for blog, changelog, pricing, support, login, signup, docs, invite, maintenance, waitlist, and shared metadata.
- Implemented dynamic metadata generation for blog, changelog, pricing, support, login, signup, docs, invite, and maintenance pages.

### How to test?

Review the changes in the PR diff to ensure the correct metadata is generated for each page.

### Why make this change?

To improve SEO and enhance user experience by providing accurate and relevant metadata for each page.

---
  • Loading branch information
alifarooq9 authored May 12, 2024
2 parents 676e55e + 6a4ce33 commit 7a60022
Show file tree
Hide file tree
Showing 35 changed files with 325 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,38 @@ import { getBlogs } from "@/server/actions/blog";
import { format } from "date-fns";
import Image from "next/image";
import { notFound, redirect } from "next/navigation";
import { type Metadata } from "next";

export const dynamic = "force-static";

type BlogSlugPageProps = {
params: {
slug: string[];
slug: string;
};
};

export async function generateMetadata({
params,
}: BlogSlugPageProps): Promise<Metadata> {
const slug = params.slug;

const blog = (await getBlogs()).find((b) => b.metaData.slug === slug);

if (!blog) {
return notFound();
}

return {
title: blog.metaData.title,
description: blog.metaData.description,
};
}

export async function generateStaticParams() {
const blogs = await getBlogs();

return blogs.map((blog) => ({
slug: blog.metaData.slug.split("/"),
slug: blog.metaData.slug,
}));
}

Expand All @@ -27,7 +45,7 @@ export default async function BlogSlugPage({ params }: BlogSlugPageProps) {
return redirect(siteUrls.blog);
}

const slug = params.slug.join("/");
const slug = params.slug;

const blog = (await getBlogs()).find((b) => b.metaData.slug === slug);

Expand Down
3 changes: 3 additions & 0 deletions starterkits/saas/src/app/(web)/blog/_constants/page-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const blogPageConfig = {
title: "Blog",
} as const;
6 changes: 6 additions & 0 deletions starterkits/saas/src/app/(web)/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import { getBlogs } from "@/server/actions/blog";
import { format } from "date-fns";
import Image from "next/image";
import Link from "next/link";
import { type Metadata } from "next";
import { blogPageConfig } from "@/app/(web)/blog/_constants/page-config";

export const metadata: Metadata = {
title: blogPageConfig.title,
};

export const dynamic = "force-static";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const changelogPageConfig = {
title: "Change Log",
} as const;
13 changes: 8 additions & 5 deletions starterkits/saas/src/app/(web)/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import { siteConfig } from "@/config/site";
import { getChangelogs } from "@/server/actions/changelog";
import { format } from "date-fns";
import Image from "next/image";
import type { Metadata } from "next";
import { changelogPageConfig } from "@/app/(web)/changelog/_constants/page-config";

export const metadata: Metadata = {
title: changelogPageConfig.title,
};

export const dynamic = "force-static";

Expand All @@ -35,11 +41,8 @@ export default async function ChangeLogPage() {
</p>
</WebPageHeader>
<div className="grid w-full max-w-4xl gap-8">
{changelogs.map((changelog) => (
<ChangeLogCard
key={changelog.metaData.slug}
{...changelog}
/>
{changelogs.map((changelog, index) => (
<ChangeLogCard key={index} {...changelog} />
))}
</div>
</WebPageWrapper>
Expand Down
7 changes: 6 additions & 1 deletion starterkits/saas/src/app/(web)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { siteUrls } from "@/config/urls";
import Image from "next/image";
import Link from "next/link";
import Balancer from "react-wrap-balancer";
import type { Metadata } from "next";

export const metadata: Metadata = {
title: "Build Your MVP in Days, not weeks. Next.js Starter Kit",
};

export const dynamic = "force-static";

Expand All @@ -19,7 +24,7 @@ export default async function HomePage() {
<WebPageWrapper>
<WebPageHeader
badge="Launch your saas in 24 hours"
title="Rapidly launch your MVP with Beautiful Starterkits, Blocks, and more."
title="Build Your MVP in Days, not weeks. Open Source Starter Kit"
>
<Balancer
as="p"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const pricingPageConfig = {
title: "Pricing",
} as const;
6 changes: 6 additions & 0 deletions starterkits/saas/src/app/(web)/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
WebPageHeader,
WebPageWrapper,
} from "@/app/(web)/_components/general-components";
import { type Metadata } from "next";
import { pricingPageConfig } from "@/app/(web)/pricing/_constants/page-config";

/**
* Customize the pricing page to your needs. You can use the `PricingPlans` component to display the pricing plans.
Expand All @@ -11,6 +13,10 @@ import {
* To customize the pricing plans, you can modify the `PricingPlans` component. @see /app/(web)/pricing/components/pricing-plans.tsx
*/

export const metadata: Metadata = {
title: pricingPageConfig.title,
};

export default function PricingPage() {
return (
<WebPageWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { siteConfig } from "@/config/site";

export const supportPageConfig = {
title: "Support",
description: `Get support from ${siteConfig.name} to get started building your next project.`,
} as const;
12 changes: 11 additions & 1 deletion starterkits/saas/src/app/(web)/support/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@ import {
import { type SupportInfo, supportInfos } from "@/config/support";
import { ArrowRightIcon } from "lucide-react";
import Link from "next/link";
import { type Metadata } from "next";
import { supportPageConfig } from "@/app/(web)/support/_constants/page-config";

export const metadata: Metadata = {
title: supportPageConfig.title,
description: supportPageConfig.description,
};

export default function ContactPage() {
return (
<WebPageWrapper>
<WebPageHeader title="Support for You" badge="Get in touch with us">
<WebPageHeader
title={supportPageConfig.title}
badge="Get in touch with us"
>
<p>
If you have any questions or need help, feel free to reach
out to us.
Expand Down
6 changes: 6 additions & 0 deletions starterkits/saas/src/app/auth/login/_constants/page-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { siteConfig } from "@/config/site";

export const loginPageConfig = {
title: "Login",
description: `Login to ${siteConfig.name} to get started building your next project.`,
} as const;
7 changes: 7 additions & 0 deletions starterkits/saas/src/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { AuthForm } from "@/app/auth/_components/auth-form";
import { loginPageConfig } from "@/app/auth/login/_constants/page-config";
import { type Metadata } from "next";

export const metadata: Metadata = {
title: loginPageConfig.title,
description: loginPageConfig.description,
};

export default function Login() {
return <AuthForm type="login" />;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { siteConfig } from "@/config/site";

export const signupPageConfig = {
title: "Signup",
description: `Signup to ${siteConfig.name} to get started building your next project.`,
} as const;
7 changes: 7 additions & 0 deletions starterkits/saas/src/app/auth/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { AuthForm } from "@/app/auth/_components/auth-form";
import { signupPageConfig } from "@/app/auth/signup/_constants/page-config";
import { type Metadata } from "next";

export const metadata: Metadata = {
title: signupPageConfig.title,
description: signupPageConfig.description,
};

export default function Signup() {
return <AuthForm type="signup" />;
Expand Down
24 changes: 20 additions & 4 deletions starterkits/saas/src/app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { notFound } from "next/navigation";
import { Toc } from "@/components/toc";
import { getDocs } from "@/server/actions/docs";
import { type Metadata } from "next";

export const dynamic = "force-static";

Expand All @@ -10,13 +11,26 @@ type DocsSlugPageProps = {
};
};

export async function generateMetadata({
params,
}: DocsSlugPageProps): Promise<Metadata> {
const slug = Array.isArray(params.slug) ? params.slug.join("/") : "/";

const doc = (await getDocs()).find((doc) => doc.metaData.slug === slug);

if (!doc) {
return notFound();
}

return {
title: doc.metaData.title,
description: doc.metaData.description,
};
}

export async function generateStaticParams() {
const docs = await getDocs();

docs.map((doc) => {
console.log(doc.metaData.slug.split("/") || ["/"]);
});

return docs.map((doc) => ({
slug: doc.metaData.slug.split("/") || ["/"],
}));
Expand All @@ -27,6 +41,8 @@ export default async function DocsSlugPage({ params }: DocsSlugPageProps) {

const doc = (await getDocs()).find((doc) => doc.metaData.slug === slug);

console.log(["gettings-started", "installation"].join("/"), params.slug);

if (!doc) {
return notFound();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const invitePageConfig = {
title: ({ orgName }: { orgName: string }) => `Invite to ${orgName}`,
description: ({ orgName }: { orgName: string }) =>
`Invite your team to ${orgName} and get started building your next project.`,
} as const;
18 changes: 17 additions & 1 deletion starterkits/saas/src/app/invite/org/[orgId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { getOrgByIdQuery } from "@/server/actions/organization/queries";
import { RequestCard } from "@/app/invite/org/[orgId]/_components/request-card";
import { notFound } from "next/navigation";
import { type Metadata } from "next";

type OrgRequestProps = {
export type OrgRequestProps = {
params: {
orgId: string;
};
Expand All @@ -23,3 +24,18 @@ export default async function OrgRequestPage({
</main>
);
}

export async function generateMetadata({
params,
}: OrgRequestProps): Promise<Metadata> {
const org = await getOrgByIdQuery({ orgId: params.orgId });

if (!org) {
return notFound();
}

return {
title: `Invite to ${org.name}`,
description: `Invite your team to ${org.name} and get started building your next project.`,
};
}
19 changes: 14 additions & 5 deletions starterkits/saas/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import { Toaster } from "@/components/ui/sonner";
import "@/styles/globals.css";
import "@/styles/prism.css";
import { fontHeading, fontSans } from "@/lib/fonts";
import { type Metadata } from "next";
import {
defaultMetadata,
twitterMetadata,
ogMetadata,
} from "@/app/shared-metadata";

export const metadata = {
title: "RapidLaunch - Next.js Boilerplate",
description:
"Next.js boilerplate with shadcn ui, TRPC, TailwindCSS, and Drizzle.",
icons: [{ rel: "icon", url: "/favicon.ico" }],
export const metadata: Metadata = {
...defaultMetadata,
twitter: {
...twitterMetadata,
},
openGraph: {
...ogMetadata,
},
};

export default function RootLayout({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const maintenancePageConfig = {
title: "Maintenance",
description:
"We&apos;re currently undergoing maintenance. Please check back later.",
} as const;
7 changes: 7 additions & 0 deletions starterkits/saas/src/app/maintenance/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { maintenancePageConfig } from "@/app/maintenance/_constants/page-config";
import { siteConfig } from "@/config/site";
import { type Metadata } from "next";

export const metadata: Metadata = {
title: maintenancePageConfig.title,
description: maintenancePageConfig.description,
};

export default function Maintenance() {
return (
Expand Down
12 changes: 12 additions & 0 deletions starterkits/saas/src/app/robots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { siteUrls } from "@/config/urls";
import type { MetadataRoute } from "next";

export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
},
sitemap: `${siteUrls.publicUrl}/sitemap.xml`,
};
}
39 changes: 39 additions & 0 deletions starterkits/saas/src/app/shared-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { siteConfig } from "@/config/site";
import { siteUrls } from "@/config/urls";
import type { Metadata } from "next";

export const defaultMetadata: Metadata = {
title: {
template: `%s | ${siteConfig.name}`,
default: siteConfig.name,
},
description: siteConfig.description,
metadataBase: new URL(siteUrls.publicUrl),
keywords: [
"Next.js",
"React",
"Next.js Starter kit",
"SaaS Starter Kit",
"Shadcn UI",
],
authors: [{ name: "Ali Farooq", url: "https://twitter.com/alifarooqdev" }],
creator: "AliFarooqDev",
};

export const twitterMetadata: Metadata["twitter"] = {
title: siteConfig.name,
description: siteConfig.description,
card: "summary_large_image",
images: [siteConfig.orgImage],
creator: "@alifarooqdev",
};

export const ogMetadata: Metadata["openGraph"] = {
title: siteConfig.name,
description: siteConfig.description,
type: "website",
images: [{ url: siteConfig.orgImage, alt: siteConfig.name }],
locale: "en_US",
url: siteUrls.publicUrl,
siteName: siteConfig.name,
};
Loading

0 comments on commit 7a60022

Please sign in to comment.