Skip to content

Commit 4fef43c

Browse files
committed
feat: v1.9.0 — cross-link projects and articles
- project cards on Projects City now link to a relevant article when one exists (InfraLens → SSRF deep-dive, Liflow → its May refonte, RepoCheckup/CookieCheckup → the new open source retrospective) - every article's footer now lists all four projects instead of always hardcoding Liflow and InfraLens - align Open Source card action links to the bottom of the card, matching the main Projects grid
1 parent fa4c3e3 commit 4fef43c

6 files changed

Lines changed: 127 additions & 67 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,22 @@ InfraLens's history as a standalone product (2026-01-06 to 2026-08-10) is
99
frozen in [`docs/infralens/CHANGELOG.md`](docs/infralens/CHANGELOG.md).
1010
InfraLens changes since its native migration are recorded here.
1111

12+
## [1.9.0] — 2026-09-06
13+
14+
### Added
15+
16+
- **Cross-links between projects and articles** — project cards on Projects
17+
City (Liflow, InfraLens, RepoCheckup, CookieCheckup) now link to a
18+
relevant article when one exists (InfraLens points to the SSRF
19+
deep-dive, Liflow to its May refonte, both OSS projects to the new
20+
open source retrospective). Every article's footer now lists all four
21+
projects, instead of always hardcoding Liflow and InfraLens.
22+
23+
### Fixed
24+
25+
- Open Source cards now align their action links to the bottom of the
26+
card (`mt-auto`), matching the main Projects grid.
27+
1228
## [1.8.3] — 2026-09-06
1329

1430
### Added

‎app/articles/[slug]/page.tsx‎

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { GitHubIcon } from "@/components/github-icon";
12
import { formatDate, getPost, posts } from "@/lib/blog";
23
import { brand } from "@/lib/brand";
34
import { buildArticleSchema } from "@/lib/json-ld";
4-
import { ArrowLeft, Clock } from "lucide-react";
5+
import { openSourceProjects } from "@/lib/open-source";
6+
import { getFeaturedProjects } from "@/lib/projects";
7+
import { ArrowLeft, Clock, ExternalLink } from "lucide-react";
58
import type { Metadata } from "next";
69
import Image from "next/image";
710
import Link from "next/link";
@@ -29,6 +32,8 @@ export default async function BlogPostPage({ params }: Props) {
2932
const post = getPost(slug);
3033
if (!post) notFound();
3134

35+
const featuredProjects = getFeaturedProjects();
36+
3237
return (
3338
<main className="flex-1 pb-20">
3439
<script
@@ -137,28 +142,39 @@ export default async function BlogPostPage({ params }: Props) {
137142
>
138143
<p className="mb-4 text-sm font-semibold text-white">Mes projets</p>
139144
<div className="flex flex-wrap gap-3">
140-
<Link
141-
href="/projects/liflow"
142-
className="inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium transition-opacity hover:opacity-80"
143-
style={{
144-
backgroundColor: "#ffffff0d",
145-
color: "#ffffff",
146-
border: "1px solid #ffffff18",
147-
}}
148-
>
149-
Découvrir Liflow →
150-
</Link>
151-
<Link
152-
href="/projects/infralens"
153-
className="inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium transition-opacity hover:opacity-80"
154-
style={{
155-
backgroundColor: "#ffffff0d",
156-
color: "#ffffff",
157-
border: "1px solid #ffffff18",
158-
}}
159-
>
160-
Découvrir InfraLens →
161-
</Link>
145+
{featuredProjects.map((project) => (
146+
<Link
147+
key={project.slug}
148+
href={`/projects/${project.slug}`}
149+
className="inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium transition-opacity hover:opacity-80"
150+
style={{
151+
backgroundColor: "#ffffff0d",
152+
color: "#ffffff",
153+
border: "1px solid #ffffff18",
154+
}}
155+
>
156+
Découvrir {project.name} →
157+
</Link>
158+
))}
159+
{openSourceProjects.map((project) => (
160+
<a
161+
key={project.name}
162+
href={project.githubUrl}
163+
target="_blank"
164+
rel="noopener noreferrer"
165+
aria-label={`Voir ${project.name} sur GitHub`}
166+
className="inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium transition-opacity hover:opacity-80"
167+
style={{
168+
backgroundColor: "#ffffff0d",
169+
color: "#ffffff",
170+
border: "1px solid #ffffff18",
171+
}}
172+
>
173+
<GitHubIcon size={13} />
174+
{project.name}
175+
<ExternalLink size={11} />
176+
</a>
177+
))}
162178
</div>
163179
</div>
164180
</div>

‎app/projects/page.tsx‎

Lines changed: 65 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -102,24 +102,35 @@ export default function ProjectsPage() {
102102
</div>
103103
</div>
104104

105-
<div className="mt-auto flex items-center gap-4">
106-
<Link
107-
href={`/projects/${project.slug}`}
108-
className="text-xs font-medium"
109-
style={{ color: cardColor }}
110-
>
111-
Étude de cas →
112-
</Link>
113-
{project.projectUrl && (
114-
<a
115-
href={project.projectUrl}
116-
target="_blank"
117-
rel="noopener noreferrer"
118-
className="inline-flex items-center gap-1 text-xs font-medium"
105+
<div className="mt-auto flex flex-col gap-2">
106+
<div className="flex items-center gap-4">
107+
<Link
108+
href={`/projects/${project.slug}`}
109+
className="text-xs font-medium"
119110
style={{ color: cardColor }}
120111
>
121-
Voir le projet <ExternalLink size={11} />
122-
</a>
112+
Étude de cas →
113+
</Link>
114+
{project.projectUrl && (
115+
<a
116+
href={project.projectUrl}
117+
target="_blank"
118+
rel="noopener noreferrer"
119+
className="inline-flex items-center gap-1 text-xs font-medium"
120+
style={{ color: cardColor }}
121+
>
122+
Voir le projet <ExternalLink size={11} />
123+
</a>
124+
)}
125+
</div>
126+
{project.relatedArticleSlug && (
127+
<Link
128+
href={`/articles/${project.relatedArticleSlug}`}
129+
className="text-xs font-medium"
130+
style={{ color: cardColor }}
131+
>
132+
Lire l&apos;article →
133+
</Link>
123134
)}
124135
</div>
125136
</article>
@@ -157,44 +168,55 @@ export default function ProjectsPage() {
157168
{project.tags.join(" · ")}
158169
</p>
159170
<p className="text-sm text-zinc-400">{project.description}</p>
160-
<div className="mt-1 flex items-center gap-4">
161-
<a
162-
href={project.githubUrl}
163-
target="_blank"
164-
rel="noopener noreferrer"
165-
aria-label={`Voir ${project.name} sur GitHub`}
166-
className="inline-flex items-center gap-1.5 text-xs font-medium"
167-
style={{ color: brand.colors.blue[400] }}
168-
>
169-
<GitHubIcon size={13} />
170-
GitHub
171-
<ExternalLink size={10} />
172-
</a>
173-
{project.href && (
171+
<div className="mt-auto flex flex-col gap-2">
172+
<div className="flex items-center gap-4">
174173
<a
175-
href={project.href}
174+
href={project.githubUrl}
176175
target="_blank"
177176
rel="noopener noreferrer"
178-
aria-label={`Voir la démo de ${project.name}`}
177+
aria-label={`Voir ${project.name} sur GitHub`}
179178
className="inline-flex items-center gap-1.5 text-xs font-medium"
180179
style={{ color: brand.colors.blue[400] }}
181180
>
182-
Voir la démo
181+
<GitHubIcon size={13} />
182+
GitHub
183183
<ExternalLink size={10} />
184184
</a>
185-
)}
186-
{project.npmUrl && (
187-
<a
188-
href={project.npmUrl}
189-
target="_blank"
190-
rel="noopener noreferrer"
191-
aria-label={`Voir ${project.name} sur npm`}
185+
{project.href && (
186+
<a
187+
href={project.href}
188+
target="_blank"
189+
rel="noopener noreferrer"
190+
aria-label={`Voir la démo de ${project.name}`}
191+
className="inline-flex items-center gap-1.5 text-xs font-medium"
192+
style={{ color: brand.colors.blue[400] }}
193+
>
194+
Voir la démo
195+
<ExternalLink size={10} />
196+
</a>
197+
)}
198+
{project.npmUrl && (
199+
<a
200+
href={project.npmUrl}
201+
target="_blank"
202+
rel="noopener noreferrer"
203+
aria-label={`Voir ${project.name} sur npm`}
204+
className="inline-flex items-center gap-1.5 text-xs font-medium"
205+
style={{ color: brand.colors.blue[400] }}
206+
>
207+
npm
208+
<ExternalLink size={10} />
209+
</a>
210+
)}
211+
</div>
212+
{project.articleSlug && (
213+
<Link
214+
href={`/articles/${project.articleSlug}`}
192215
className="inline-flex items-center gap-1.5 text-xs font-medium"
193216
style={{ color: brand.colors.blue[400] }}
194217
>
195-
npm
196-
<ExternalLink size={10} />
197-
</a>
218+
Lire l&apos;article →
219+
</Link>
198220
)}
199221
</div>
200222
</div>

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "randy-code",
3-
"version": "1.8.3",
3+
"version": "1.9.0",
44
"private": true,
55
"packageManager": "pnpm@10.7.0",
66
"scripts": {

‎src/lib/open-source.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface OpenSourceProject {
55
href?: string;
66
githubUrl: string;
77
npmUrl?: string;
8+
articleSlug?: string;
89
}
910

1011
export const openSourceProjects: OpenSourceProject[] = [
@@ -15,6 +16,7 @@ export const openSourceProjects: OpenSourceProject[] = [
1516
tags: ["CLI", "TypeScript", "npm"],
1617
githubUrl: "https://github.com/Randy-R-code/repo-checkup",
1718
npmUrl: "https://www.npmjs.com/package/repo-checkup",
19+
articleSlug: "construire-moins-de-projets-vraiment-utiles",
1820
},
1921
{
2022
name: "CookieCheckup",
@@ -23,5 +25,6 @@ export const openSourceProjects: OpenSourceProject[] = [
2325
tags: ["Next.js", "React", "TypeScript"],
2426
href: "https://cookie-checkup.vercel.app",
2527
githubUrl: "https://github.com/Randy-R-code/cookie-checkup",
28+
articleSlug: "construire-moins-de-projets-vraiment-utiles",
2629
},
2730
];

‎src/lib/projects.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface Project {
2525
image?: string;
2626
featured: boolean;
2727
caseStudy?: ProjectCaseStudy;
28+
relatedArticleSlug?: string;
2829
}
2930

3031
export const projects: Project[] = [
@@ -44,6 +45,7 @@ export const projects: Project[] = [
4445
technologies: ["Next.js", "TypeScript", "Mistral AI", "Mux", "Upstash"],
4546
projectUrl: "https://liflow.app",
4647
featured: true,
48+
relatedArticleSlug: "liflow-refonte-souvenirs-familiaux",
4749
caseStudy: {
4850
context:
4951
'Liflow a démarré comme un SaaS de partage classique, pensé autour de "capsules" (notes, fichiers, événements partagés par groupe). En devenant père, je me suis retrouvé avec des centaines de photos et de souvenirs dispersés, jamais revisités — ce changement personnel a déclenché une refonte complète du produit, avec la sortie de Next.js 16 comme fenêtre technique pour tout reconstruire.',
@@ -79,6 +81,7 @@ export const projects: Project[] = [
7981
technologies: ["Next.js", "TypeScript", "Node.js", "Vercel"],
8082
projectUrl: "https://randy-code.dev/tools/infralens",
8183
featured: true,
84+
relatedArticleSlug: "securiser-analyseur-url-contre-ssrf",
8285
caseStudy: {
8386
context:
8487
"InfraLens est né du besoin de vérifier rapidement la configuration technique d'un site (DNS, sécurité, headers) sans jongler entre plusieurs outils différents. Conçu et maintenu en solo, en parallèle de projets clients, comme outil gratuit et open source plutôt que comme produit commercial.",

0 commit comments

Comments
 (0)