blog article summary
This commit is contained in:
@@ -2,6 +2,7 @@ import { notFound } from "next/navigation";
|
|||||||
import { MDXRemote } from "next-mdx-remote/rsc";
|
import { MDXRemote } from "next-mdx-remote/rsc";
|
||||||
import { getArticleBySlug, getArticleSlugs } from "@/lib/blog/get-articles";
|
import { getArticleBySlug, getArticleSlugs } from "@/lib/blog/get-articles";
|
||||||
import { ArticleHero } from "@/components/sections/ArticleHero";
|
import { ArticleHero } from "@/components/sections/ArticleHero";
|
||||||
|
import { ArticleSummary } from "@/components/sections/ArticleSummary";
|
||||||
import { locales } from "@/middleware";
|
import { locales } from "@/middleware";
|
||||||
|
|
||||||
type props = {
|
type props = {
|
||||||
@@ -43,6 +44,9 @@ export default async function ArticlePage({ params }: props) {
|
|||||||
cover={article.cover}
|
cover={article.cover}
|
||||||
/>
|
/>
|
||||||
<div className="mx-auto max-w-[720px] px-8 md:px-14 lg:px-6 py-20">
|
<div className="mx-auto max-w-[720px] px-8 md:px-14 lg:px-6 py-20">
|
||||||
|
{article.summary && article.summary.length > 0 && (
|
||||||
|
<ArticleSummary items={article.summary} />
|
||||||
|
)}
|
||||||
{article.tags.length > 0 && (
|
{article.tags.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-2 mb-14">
|
<div className="flex flex-wrap gap-2 mb-14">
|
||||||
{article.tags.map((tag) => (
|
{article.tags.map((tag) => (
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRef, useEffect } from 'react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
type props = {
|
||||||
|
items: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ArticleSummary({ items }: props) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const t = useTranslations('blogPage');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const el = ref.current;
|
||||||
|
if (!el) return;
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
el.querySelectorAll('.reveal').forEach((node, i) => {
|
||||||
|
setTimeout(() => node.classList.add('visible'), i * 80);
|
||||||
|
});
|
||||||
|
observer.unobserve(el);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
observer.observe(el);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="mb-14 border border-brand-brown/12 rounded-xl p-8 bg-brand-brown/[0.03]">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<span className="w-5 h-px bg-brand-brown/30" />
|
||||||
|
<span className="font-sans text-[10px] uppercase tracking-[0.22em] text-brand-brown/40">
|
||||||
|
{t('summary.label')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ol className="flex flex-col gap-3">
|
||||||
|
{items.map((item, i) => (
|
||||||
|
<li key={i} className="reveal flex items-baseline gap-4" style={{ transitionDelay: `${i * 60}ms` }}>
|
||||||
|
<span className="font-serif text-base text-brand-brown/25 tabular-nums flex-shrink-0 w-4 text-right">
|
||||||
|
{String(i + 1).padStart(2, '0')}
|
||||||
|
</span>
|
||||||
|
<span className="font-sans text-sm text-brand-brown/65 leading-relaxed">
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,6 +10,11 @@ tags:
|
|||||||
- "Scalability"
|
- "Scalability"
|
||||||
- "Cloud"
|
- "Cloud"
|
||||||
readTime: "8 min"
|
readTime: "8 min"
|
||||||
|
summary:
|
||||||
|
- "Good architecture starts with understanding the real constraints of the system, not by picking a technology."
|
||||||
|
- "A monolith is often the best starting point; microservices are justified by organizational maturity, not project size."
|
||||||
|
- "Scalability must be designed upfront: stateless services, event-driven decoupling, multi-level caching."
|
||||||
|
- "Technical debt is inevitable, but it must be actively managed to avoid paralyzing the product in the medium term."
|
||||||
---
|
---
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ tags:
|
|||||||
- "Scalabilité"
|
- "Scalabilité"
|
||||||
- "Cloud"
|
- "Cloud"
|
||||||
readTime: "8 min"
|
readTime: "8 min"
|
||||||
|
summary:
|
||||||
|
- "Poser les bonnes fondations"
|
||||||
|
- "Monolithe ou microservices ?"
|
||||||
|
- "La scalabilité, une propriété à concevoir dès le départ"
|
||||||
|
- "La dette technique : l'ennemi silencieux"
|
||||||
---
|
---
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+4
-1
@@ -68,7 +68,10 @@
|
|||||||
"backToBlog": "Back to blog",
|
"backToBlog": "Back to blog",
|
||||||
"scrollPrompt": "Scroll",
|
"scrollPrompt": "Scroll",
|
||||||
"readArticle": "Read article",
|
"readArticle": "Read article",
|
||||||
"noArticles": "No articles yet."
|
"noArticles": "No articles yet.",
|
||||||
|
"summary": {
|
||||||
|
"label": "Summary"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"projectPage": {
|
"projectPage": {
|
||||||
"hero": {
|
"hero": {
|
||||||
|
|||||||
+4
-1
@@ -68,7 +68,10 @@
|
|||||||
"backToBlog": "Retour au blog",
|
"backToBlog": "Retour au blog",
|
||||||
"scrollPrompt": "Défiler",
|
"scrollPrompt": "Défiler",
|
||||||
"readArticle": "Lire l'article",
|
"readArticle": "Lire l'article",
|
||||||
"noArticles": "Aucun article pour le moment."
|
"noArticles": "Aucun article pour le moment.",
|
||||||
|
"summary": {
|
||||||
|
"label": "En résumé"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"projectPage": {
|
"projectPage": {
|
||||||
"hero": {
|
"hero": {
|
||||||
|
|||||||
Reference in New Issue
Block a user