diff --git a/__tests__/components/ArticleCard.test.tsx b/__tests__/components/ArticleCard.test.tsx
new file mode 100644
index 0000000..458eb11
--- /dev/null
+++ b/__tests__/components/ArticleCard.test.tsx
@@ -0,0 +1,43 @@
+import { describe, it, expect } from 'vitest'
+import { render, screen } from '@testing-library/react'
+import ArticleCard from '@/components/article/ArticleCard'
+import type { Article } from '@/lib/types'
+
+const mockArticle: Article = {
+ id: '1',
+ title: 'Frieren Season 2 Officially Announced',
+ slug: 'frieren-season-2-announced',
+ status: 'published',
+ content: null,
+ excerpt: 'The beloved series returns.',
+ featured_image: null,
+ published_at: '2026-05-28T00:00:00Z',
+ is_featured: false,
+ seo_title: null,
+ seo_description: null,
+ category: { id: '1', name: 'Anime', slug: 'anime', description: null },
+ tags: [],
+}
+
+describe('ArticleCard', () => {
+ it('renders the article title', () => {
+ render()
+ expect(screen.getByText('Frieren Season 2 Officially Announced')).toBeInTheDocument()
+ })
+
+ it('renders the category name', () => {
+ render()
+ expect(screen.getByText('Anime')).toBeInTheDocument()
+ })
+
+ it('links to the correct article URL', () => {
+ render()
+ const link = screen.getByRole('link')
+ expect(link).toHaveAttribute('href', '/anime/frieren-season-2-announced')
+ })
+
+ it('renders the Read -> CTA', () => {
+ render()
+ expect(screen.getByText('Read →')).toBeInTheDocument()
+ })
+})
diff --git a/components/article/ArticleCard.tsx b/components/article/ArticleCard.tsx
new file mode 100644
index 0000000..11fb3b7
--- /dev/null
+++ b/components/article/ArticleCard.tsx
@@ -0,0 +1,42 @@
+import Image from 'next/image'
+import Link from 'next/link'
+import { getAssetUrl } from '@/lib/directus'
+import type { Article } from '@/lib/types'
+
+interface Props {
+ article: Article
+}
+
+export default function ArticleCard({ article }: Props) {
+ const href = `/${article.category.slug}/${article.slug}`
+
+ return (
+
+
+ {article.featured_image ? (
+
+ ) : (
+
+ )}
+
+
+
+ {article.category.name}
+
+
+ {article.title}
+
+
Read →
+
+
+ )
+}
diff --git a/lib/directus.ts b/lib/directus.ts
index 0e70e3d..54096f6 100644
--- a/lib/directus.ts
+++ b/lib/directus.ts
@@ -8,12 +8,19 @@ import {
} from '@directus/sdk'
import type { Article, Category, SiteSettings } from './types'
-const directus = createDirectus(process.env.DIRECTUS_URL!)
- .with(staticToken(process.env.DIRECTUS_TOKEN!))
- .with(rest())
+let _client: ReturnType | null = null
+
+function getClient() {
+ if (!_client) {
+ _client = createDirectus(process.env.DIRECTUS_URL!)
+ .with(staticToken(process.env.DIRECTUS_TOKEN!))
+ .with(rest())
+ }
+ return _client
+}
export async function getAllCategories(): Promise {
- return directus.request(
+ return getClient().request(
readItems('categories', {
fields: ['id', 'name', 'slug', 'description'],
sort: ['name'],
@@ -22,7 +29,7 @@ export async function getAllCategories(): Promise {
}
export async function getCategoryBySlug(slug: string): Promise {
- const results = await directus.request(
+ const results = await getClient().request(
readItems('categories', {
fields: ['id', 'name', 'slug', 'description'],
filter: { slug: { _eq: slug } },
@@ -42,7 +49,7 @@ export async function getArticles(options: {
if (categorySlug) {
filter['category'] = { slug: { _eq: categorySlug } }
}
- return directus.request(
+ return getClient().request(
readItems('articles', {
fields: [
'id', 'title', 'slug', 'status', 'excerpt', 'featured_image',
@@ -58,7 +65,7 @@ export async function getArticles(options: {
}
export async function getArticleBySlug(slug: string): Promise {
- const results = await directus.request(
+ const results = await getClient().request(
readItems('articles', {
fields: [
'id', 'title', 'slug', 'status', 'content', 'excerpt',
@@ -79,7 +86,7 @@ export async function getArticlePathById(id: string): Promise<{
category: { slug: string }
} | null> {
try {
- return await directus.request(
+ return await getClient().request(
readItem('articles', id, {
fields: ['slug', 'category.slug'],
})
@@ -93,7 +100,7 @@ export async function getRelatedArticles(
categorySlug: string,
excludeSlug: string,
): Promise {
- return directus.request(
+ return getClient().request(
readItems('articles', {
fields: [
'id', 'title', 'slug', 'excerpt', 'featured_image',
@@ -111,7 +118,7 @@ export async function getRelatedArticles(
}
export async function getSiteSettings(): Promise {
- return directus.request(
+ return getClient().request(
readSingleton('site_settings', {
fields: [
'id', 'site_name',
@@ -125,7 +132,7 @@ export async function getSiteSettings(): Promise {
}
export async function searchArticles(query: string): Promise {
- return directus.request(
+ return getClient().request(
readItems('articles', {
fields: ['id', 'title', 'slug', 'category.slug', 'category.name'],
search: query,