diff --git a/src/app/posts/layout.tsx b/src/app/posts/layout.tsx
index 4093c5c9..9a4d1121 100644
--- a/src/app/posts/layout.tsx
+++ b/src/app/posts/layout.tsx
@@ -1,5 +1,8 @@
+import React from 'react';
import { Metadata } from 'next';
import postsMeta from '@lib/meta/posts';
+import BreadcrumbStructuredData from '@components/breadcrumb-structured-data';
+import { NEXT_PUBLIC_APP_URL } from '@lib/constants';
export const metadata: Metadata = {
...postsMeta,
@@ -13,5 +16,13 @@ export const metadata: Metadata = {
export default function PostsLayout({ children, }: {
children: React.ReactNode;
}) {
- return (<> {children} >);
+ return (
+ <>
+
+ {children}
+ >
+ );
}
diff --git a/src/app/projects/layout.tsx b/src/app/projects/layout.tsx
index 47d923c1..483aafc7 100644
--- a/src/app/projects/layout.tsx
+++ b/src/app/projects/layout.tsx
@@ -1,10 +1,21 @@
+import React from 'react';
import { Metadata } from 'next';
import { projectsMeta } from '@lib/meta/projects';
+import BreadcrumbStructuredData from '@components/breadcrumb-structured-data';
+import { NEXT_PUBLIC_APP_URL } from '@lib/constants';
export const metadata: Metadata = projectsMeta;
export default function ProjectsLayout({ children, }: {
children: React.ReactNode;
}) {
- return (<> {children} >);
+ return (
+ <>
+
+ {children}
+ >
+ );
}
diff --git a/src/components/about-structured-data.tsx b/src/components/about-structured-data.tsx
index d068f2c6..3e2af7b8 100644
--- a/src/components/about-structured-data.tsx
+++ b/src/components/about-structured-data.tsx
@@ -13,9 +13,16 @@ import {
export default function AboutStructuredData() {
const structuredData = {
"@context": "https://schema.org",
- "@type": "AboutPage",
+ "@type": "ProfilePage",
+ "@id": `${NEXT_PUBLIC_APP_URL}/about`,
+ "name": `About ${appName}`,
+ "description": `I'm ${PERSON_NAME} (${PERSON_ALTERNATE_NAME}), a ${PERSON_JOB_TITLE}.`,
+ "url": `${NEXT_PUBLIC_APP_URL}/about`,
+ "dateCreated": "2021-01-01",
+ "dateModified": new Date().toISOString().split('T')[0],
"mainEntity": {
"@type": "Person",
+ "@id": `${NEXT_PUBLIC_APP_URL}#person`,
"name": PERSON_NAME,
"alternateName": PERSON_ALTERNATE_NAME,
"description": `I'm ${PERSON_NAME} (${PERSON_ALTERNATE_NAME}), a ${PERSON_JOB_TITLE}.`,
@@ -29,10 +36,7 @@ export default function AboutStructuredData() {
GITHUB_URL,
LINKEDIN_URL
]
- },
- "name": `About ${appName}`,
- "description": `I'm ${PERSON_NAME} (${PERSON_ALTERNATE_NAME}), a ${PERSON_JOB_TITLE}.`,
- "url": `${NEXT_PUBLIC_APP_URL}/about`
+ }
};
return (
diff --git a/src/components/data-structured/article.tsx b/src/components/data-structured/article.tsx
index 18acb948..167ade04 100644
--- a/src/components/data-structured/article.tsx
+++ b/src/components/data-structured/article.tsx
@@ -55,7 +55,7 @@ export default function ArticleStructuredData({
const structuredData = {
"@context": "https://schema.org",
- "@type": "Article",
+ "@type": "BlogPosting",
"@id": `${NEXT_PUBLIC_APP_URL}/posts/${slug}`,
"headline": title,
"description": description,
diff --git a/src/components/data-structured/faq-page.tsx b/src/components/data-structured/faq-page.tsx
new file mode 100644
index 00000000..27a0f8ea
--- /dev/null
+++ b/src/components/data-structured/faq-page.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+
+interface FAQItem {
+ question: string;
+ answer: string;
+}
+
+interface FAQPageStructuredDataProps {
+ items: FAQItem[];
+}
+
+export default function FAQPageStructuredData({ items }: FAQPageStructuredDataProps) {
+ const structuredData = {
+ "@context": "https://schema.org",
+ "@type": "FAQPage",
+ "mainEntity": items.map((item) => ({
+ "@type": "Question",
+ "name": item.question,
+ "acceptedAnswer": {
+ "@type": "Answer",
+ "text": item.answer
+ }
+ }))
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/data-structured/software-application.tsx b/src/components/data-structured/software-application.tsx
new file mode 100644
index 00000000..20acc419
--- /dev/null
+++ b/src/components/data-structured/software-application.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import { NEXT_PUBLIC_APP_URL, PERSON_NAME, appName } from '@lib/constants';
+
+interface SoftwareApplicationStructuredDataProps {
+ name: string;
+ description: string;
+ url: string;
+ repositoryUrl?: string;
+ applicationCategory?: string;
+ operatingSystem?: string;
+ screenshots?: string[];
+ datePublished?: string;
+ keywords?: string[];
+}
+
+export default function SoftwareApplicationStructuredData({
+ name,
+ description,
+ url,
+ repositoryUrl,
+ applicationCategory = 'WebApplication',
+ operatingSystem = 'Web Browser',
+ screenshots = [],
+ datePublished,
+ keywords = []
+}: SoftwareApplicationStructuredDataProps) {
+ const structuredData = {
+ "@context": "https://schema.org",
+ "@type": "SoftwareApplication",
+ "name": name,
+ "description": description,
+ "url": url,
+ "applicationCategory": applicationCategory,
+ "operatingSystem": operatingSystem,
+ "offers": {
+ "@type": "Offer",
+ "price": "0",
+ "priceCurrency": "USD"
+ },
+ "author": {
+ "@type": "Person",
+ "@id": `${NEXT_PUBLIC_APP_URL}#person`,
+ "name": PERSON_NAME
+ },
+ "creator": {
+ "@type": "Person",
+ "@id": `${NEXT_PUBLIC_APP_URL}#person`,
+ "name": PERSON_NAME
+ },
+ "publisher": {
+ "@type": "Person",
+ "name": appName,
+ "url": NEXT_PUBLIC_APP_URL
+ },
+ ...(datePublished && { "datePublished": datePublished }),
+ ...(keywords.length > 0 && { "keywords": keywords.join(', ') }),
+ ...(repositoryUrl && { "codeRepository": repositoryUrl }),
+ ...(screenshots.length > 0 && {
+ "screenshot": screenshots.map(src => ({
+ "@type": "ImageObject",
+ "url": src.startsWith('http') ? src : `${NEXT_PUBLIC_APP_URL}${src}`
+ }))
+ })
+ };
+
+ return (
+
+ );
+}