Skip to content

Commit 1dbf222

Browse files
chore(release): bump version to 3.1.0, update project links in llms.txt, enhance SEO metadata for project pages, and improve robots.txt configuration
1 parent 14ebd22 commit 1dbf222

19 files changed

Lines changed: 429 additions & 84 deletions

File tree

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cipher-portfolio",
3-
"version": "3.0.0",
3+
"version": "3.1.0",
44
"private": false,
55
"description": "Official portfolio website for Cipher Unit — an open-source engineering collective building secure, scalable developer tools.",
66
"license": "MIT",

‎public/llms.txt‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919

2020
## Projects
2121

22-
- Cipher Token: https://github.com/cipherunits/CipherToken — high-performance Rust JWT/crypto library with Python (PyO3) bindings
22+
- Cipher Token: https://cipherunit.xyz/projects/cipher-token — high-performance Rust JWT/crypto library with Python (PyO3) bindings
23+
- GitHub: https://github.com/cipherunits/CipherToken
2324
- Image: https://cipherunit.xyz/images/cipher-token.png
24-
- NPM Mirror: https://github.com/cipherunits/npm-mirror — offline npm package mirror with Docker Compose
25+
- NPM Mirror: https://cipherunit.xyz/projects/npm-mirror — offline npm package mirror with Docker Compose
26+
- GitHub: https://github.com/cipherunits/npm-mirror
2527
- Image: https://cipherunit.xyz/images/npm-mirrors.png
2628

2729
## Team

‎scripts/seo-notify.mjs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const INDEXNOW_KEY = "cipherunit-indexnow-2026";
1111
const urls = [
1212
`${SITE}/`,
1313
`${SITE}/projects`,
14+
`${SITE}/projects/cipher-token`,
15+
`${SITE}/projects/npm-mirror`,
1416
`${SITE}/team`,
1517
`${SITE}/contact`,
1618
`${SITE}/sitemap.xml`,
@@ -47,6 +49,8 @@ async function main() {
4749
for (const url of [
4850
`${SITE}/`,
4951
`${SITE}/projects`,
52+
`${SITE}/projects/cipher-token`,
53+
`${SITE}/projects/npm-mirror`,
5054
`${SITE}/team`,
5155
`${SITE}/contact`,
5256
]) {

‎src/app/contact/page.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export default function ContactPage() {
7979
<JsonLd id="jsonld-breadcrumb-contact" data={BreadcrumbJsonLdContact} />
8080

8181
<header className="space-y-3">
82-
<NamePage />
82+
<NamePage title="Contact CipherUnit" />
8383
<SubNamePage text="Contact CipherUnit" />
8484
</header>
8585

‎src/app/image-sitemap.xml/route.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import {
22
brandImageUrls,
33
buildImageSitemapXml,
4+
projectImageCaption,
5+
projectImageTitle,
6+
projectPagePath,
47
projectSitemapImages,
58
teamSitemapImages,
69
} from "@/lib/seo-images";
10+
import { projects } from "@/lib/projects";
711
import { absoluteUrl, siteConfig } from "@/lib/site";
812
import { getTeamMembers } from "@/lib/team";
913

@@ -31,6 +35,16 @@ export async function GET() {
3135
pageUrl: absoluteUrl("/projects"),
3236
images: projectSitemapImages(),
3337
},
38+
...projects.map((project) => ({
39+
pageUrl: absoluteUrl(projectPagePath(project)),
40+
images: [
41+
{
42+
loc: absoluteUrl(project.imageUrl),
43+
title: projectImageTitle(project),
44+
caption: projectImageCaption(project),
45+
},
46+
],
47+
})),
3448
{
3549
pageUrl: absoluteUrl("/team"),
3650
images: teamSitemapImages(members),

‎src/app/projects/[slug]/page.tsx‎

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import type { Metadata } from "next";
2+
import Image from "next/image";
3+
import Link from "next/link";
4+
import { notFound } from "next/navigation";
5+
6+
import Button from "@/components/shared/Button";
7+
import {
8+
buildProjectPageJsonLd,
9+
JsonLd,
10+
} from "@/components/shared/JsonLd";
11+
import NamePage from "@/components/shared/NamePage";
12+
import SubNamePage from "@/components/shared/SubNamePage";
13+
import { getProjectBySlug, projects } from "@/lib/projects";
14+
import {
15+
projectImageCaption,
16+
projectImageTitle,
17+
projectPagePath,
18+
} from "@/lib/seo-images";
19+
import { absoluteUrl, brandOgImages, siteConfig } from "@/lib/site";
20+
21+
type ProjectPageProps = {
22+
params: Promise<{ slug: string }>;
23+
};
24+
25+
export function generateStaticParams() {
26+
return projects.map((project) => ({ slug: project.slug }));
27+
}
28+
29+
export const dynamicParams = false;
30+
31+
export async function generateMetadata({
32+
params,
33+
}: ProjectPageProps): Promise<Metadata> {
34+
const { slug } = await params;
35+
const project = getProjectBySlug(slug);
36+
37+
if (!project) {
38+
return {
39+
title: { absolute: "Project not found | Cipher Unit" },
40+
robots: { index: false, follow: true },
41+
};
42+
}
43+
44+
const pageUrl = absoluteUrl(projectPagePath(project));
45+
const title = `${project.title} | Cipher Unit Open Source Project`;
46+
47+
return {
48+
title: { absolute: title },
49+
description: project.description,
50+
keywords: [
51+
project.title,
52+
"Cipher Unit",
53+
"CipherUnit",
54+
"open source",
55+
...project.programmingLanguages,
56+
],
57+
alternates: {
58+
canonical: projectPagePath(project),
59+
},
60+
authors: [{ name: "Cipher Unit", url: siteConfig.url }],
61+
creator: "Cipher Unit",
62+
publisher: "Cipher Unit",
63+
robots: {
64+
index: true,
65+
follow: true,
66+
googleBot: {
67+
index: true,
68+
follow: true,
69+
"max-image-preview": "large",
70+
"max-snippet": -1,
71+
"max-video-preview": -1,
72+
},
73+
},
74+
openGraph: {
75+
type: "website",
76+
locale: "en_US",
77+
url: pageUrl,
78+
siteName: siteConfig.name,
79+
title: `${project.title} — Cipher Unit`,
80+
description: project.description,
81+
images: [
82+
{
83+
url: project.imageUrl,
84+
alt: projectImageTitle(project),
85+
type: "image/png" as const,
86+
},
87+
...brandOgImages(`${project.title} by Cipher Unit`),
88+
],
89+
},
90+
twitter: {
91+
card: "summary_large_image",
92+
title: `${project.title} — Cipher Unit`,
93+
description: project.description,
94+
images: [project.imageUrl],
95+
},
96+
};
97+
}
98+
99+
export default async function ProjectPage({ params }: ProjectPageProps) {
100+
const { slug } = await params;
101+
const project = getProjectBySlug(slug);
102+
103+
if (!project) {
104+
notFound();
105+
}
106+
107+
const imageTitle = projectImageTitle(project);
108+
const imageCaption = projectImageCaption(project);
109+
110+
return (
111+
<main
112+
id="main-content"
113+
className="mx-auto my-8 max-w-6xl px-4 sm:px-6 md:my-22"
114+
>
115+
<JsonLd
116+
id={`jsonld-project-${project.slug}`}
117+
data={buildProjectPageJsonLd(project)}
118+
/>
119+
120+
<header className="mb-10 space-y-2">
121+
<NamePage title={project.title} />
122+
<SubNamePage text={project.description} />
123+
</header>
124+
125+
<article
126+
itemScope
127+
itemType="https://schema.org/SoftwareSourceCode"
128+
className="overflow-hidden rounded-xl border border-(--color-stroke)/50 bg-(--color-background-secondary)"
129+
>
130+
<meta itemProp="codeRepository" content={project.linkLive} />
131+
<meta
132+
itemProp="url"
133+
content={absoluteUrl(projectPagePath(project))}
134+
/>
135+
<meta itemProp="applicationCategory" content="Developer Tool" />
136+
<meta itemProp="author" content="Cipher Unit" />
137+
<meta itemProp="creator" content="Cipher Unit" />
138+
139+
<figure
140+
className="relative aspect-video w-full overflow-hidden"
141+
itemProp="image"
142+
itemScope
143+
itemType="https://schema.org/ImageObject"
144+
>
145+
<meta
146+
itemProp="contentUrl"
147+
content={absoluteUrl(project.imageUrl)}
148+
/>
149+
<meta itemProp="url" content={absoluteUrl(project.imageUrl)} />
150+
<meta itemProp="name" content={imageTitle} />
151+
<meta itemProp="caption" content={imageCaption} />
152+
<Image
153+
src={project.imageUrl}
154+
alt={`${project.title} open source software developed by Cipher Unit (CipherUnit). ${project.description}`}
155+
title={imageTitle}
156+
fill
157+
priority
158+
quality={90}
159+
sizes="(max-width: 1024px) 100vw, 1152px"
160+
className="object-cover"
161+
/>
162+
<figcaption className="sr-only">{imageCaption}</figcaption>
163+
</figure>
164+
165+
<div className="border-y border-(--color-stroke)/40 bg-(--color-surface)/40 px-5 py-3">
166+
<p className="text-sm leading-6 text-(--color-stroke)">
167+
{project.tech}
168+
</p>
169+
{project.programmingLanguages.map((lang) => (
170+
<meta key={lang} itemProp="programmingLanguage" content={lang} />
171+
))}
172+
</div>
173+
174+
<div className="space-y-6 p-6 sm:p-8">
175+
<div>
176+
<h2
177+
itemProp="name"
178+
className="text-3xl font-semibold text-white"
179+
>
180+
{project.title}
181+
</h2>
182+
<p
183+
itemProp="description"
184+
className="mt-4 max-w-3xl text-sm leading-7 text-(--color-stroke)"
185+
>
186+
{project.description}
187+
</p>
188+
</div>
189+
190+
<nav
191+
aria-label={`${project.title} project links`}
192+
className="flex flex-wrap items-center gap-3"
193+
>
194+
<Link
195+
href={project.linkLive}
196+
target="_blank"
197+
rel="noopener noreferrer"
198+
aria-label={`Open ${project.title} GitHub repository`}
199+
itemProp="codeRepository"
200+
>
201+
<Button Theme="primary">
202+
{project.buttonLive ?? "GitHub Repository"}
203+
</Button>
204+
</Link>
205+
<Link
206+
href={project.linkDocs}
207+
target="_blank"
208+
rel="noopener noreferrer"
209+
aria-label={`Read ${project.title} documentation`}
210+
>
211+
<Button Theme="stroke">
212+
{project.buttonDocs ?? "Documentation"}
213+
</Button>
214+
</Link>
215+
<Link
216+
href="/projects"
217+
className="text-sm text-(--color-stroke) transition hover:text-white"
218+
>
219+
← All projects
220+
</Link>
221+
</nav>
222+
</div>
223+
</article>
224+
</main>
225+
);
226+
}

‎src/app/projects/page.tsx‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ const projectOgImages = projects.map((project) => ({
2020
}));
2121

2222
export const metadata: Metadata = {
23-
title:
24-
"Open Source Projects | Cipher Unit - Rust, Python, Docker & Developer Tools",
23+
title: {
24+
absolute:
25+
"Open Source Projects | Cipher Unit - Rust, Python, Docker & Developer Tools",
26+
},
2527
description:
2628
"Explore Cipher Unit's open-source software projects, including Rust libraries, Python packages, Docker utilities, cryptography tools and production-ready developer solutions.",
2729
keywords: [
@@ -93,7 +95,7 @@ export default function ProjectsPage() {
9395
<JsonLd id="jsonld-projects-graph" data={buildProjectsGraphJsonLd()} />
9496

9597
<header className="mb-10 space-y-2">
96-
<NamePage />
98+
<NamePage title="Open Source Projects" />
9799
<SubNamePage text="Explore all open-source projects developed by Cipher Unit." />
98100
</header>
99101

0 commit comments

Comments
 (0)