From fbc231dc9ae6b5f6ef5b00c6431911e897547b3a Mon Sep 17 00:00:00 2001
From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com>
Date: Wed, 14 Jan 2026 16:37:55 +0100
Subject: [PATCH] refactor!: remove unused resizable components and enhance
efficiency in critical areas
Remove outdated `ResizablePanelGroup`, `ResizablePanel`, and `ResizableHandle` components from the codebase. Optimize API error handling with anti-spam protection for repetitive errors. Update Dockerfile for streamlined builds, improve sitemap generation in `app`, and refactor lazy loading using `React.Suspense`. Refine services to support enhanced query parameters like `author`.
---
frontend/Dockerfile | 65 +++++++-------------
frontend/src/app/(dashboard)/layout.tsx | 8 ++-
frontend/src/app/(dashboard)/recent/page.tsx | 8 +--
frontend/src/app/(dashboard)/trends/page.tsx | 8 +--
frontend/src/app/sitemap.ts | 2 +-
frontend/src/components/ui/resizable.tsx | 56 -----------------
frontend/src/lib/api.ts | 27 ++++++++
frontend/src/services/content.service.ts | 1 +
8 files changed, 61 insertions(+), 114 deletions(-)
delete mode 100644 frontend/src/components/ui/resizable.tsx
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index ef10474..a26b050 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -1,66 +1,45 @@
# syntax=docker.io/docker/dockerfile:1
-FROM pnpm/pnpm:20-alpine AS base
+FROM node:22-alpine AS base
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+RUN corepack enable && corepack prepare pnpm@latest --activate
-# Install dependencies only when needed
-FROM base AS deps
-# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
-RUN apk add --no-cache libc6-compat
-WORKDIR /app
-
-# Install dependencies based on the preferred package manager
-COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* source.config.ts* next.config.* ./
-RUN \
- if [ -f pnpm-lock.yaml ]; then pnpm i --frozen-lockfile; \
- elif [ -f package-lock.json ]; then npm ci; \
- elif [ -f yarn.lock ]; then yarn --frozen-lockfile; \
- else echo "Lockfile not found." && exit 1; \
- fi
-
-
-# Rebuild the source code only when needed
FROM base AS builder
-WORKDIR /app
-COPY --from=deps /app/node_modules ./node_modules
+WORKDIR /usr/src/app
+COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
+COPY backend/package.json ./backend/
+COPY frontend/package.json ./frontend/
+COPY documentation/package.json ./documentation/
+RUN pnpm install --no-frozen-lockfile
COPY . .
+# On réinstalle après COPY pour s'assurer que tous les scripts de cycle de vie et les liens sont corrects
+RUN pnpm install --no-frozen-lockfile
+RUN pnpm run --filter @memegoat/frontend build
-# Next.js collects completely anonymous telemetry data about general usage.
-# Learn more here: https://nextjs.org/telemetry
-# Uncomment the following line in case you want to disable telemetry during the build.
-# ENV NEXT_TELEMETRY_DISABLED=1
-
-RUN \
- if [ -f pnpm-lock.yaml ]; then pnpm run build; \
- elif [ -f package-lock.json ]; then npm run build; \
- elif [ -f yarn.lock ]; then yarn run build; \
- else echo "Lockfile not found." && exit 1; \
- fi
-
-# Production image, copy all the files and run next
-FROM base AS runner
+FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
-# Uncomment the following line in case you want to disable telemetry during runtime.
-# ENV NEXT_TELEMETRY_DISABLED=1
+ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
-COPY --from=builder /app/public ./public
+COPY --from=builder /usr/src/app/frontend/public ./frontend/public
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
-COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
-COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
+COPY --from=builder --chown=nextjs:nodejs /usr/src/app/frontend/.next/standalone ./
+COPY --from=builder --chown=nextjs:nodejs /usr/src/app/frontend/.next/static ./frontend/.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
-
-# server.js is created by next build from the standalone output
-# https://nextjs.org/docs/pages/api-reference/config/next-config-js/output
ENV HOSTNAME="0.0.0.0"
-CMD ["node", "server.js"]
+
+# Note: server.js is created in the standalone output.
+# In a monorepo, it's often inside a subdirectory matching the package name.
+CMD ["node", "frontend/server.js"]
diff --git a/frontend/src/app/(dashboard)/layout.tsx b/frontend/src/app/(dashboard)/layout.tsx
index 671e9cf..792cba6 100644
--- a/frontend/src/app/(dashboard)/layout.tsx
+++ b/frontend/src/app/(dashboard)/layout.tsx
@@ -24,9 +24,13 @@ export default function DashboardLayout({
{children}
{modal}
-
+
+
+
-
+
+
+
);
diff --git a/frontend/src/app/(dashboard)/recent/page.tsx b/frontend/src/app/(dashboard)/recent/page.tsx
index c892c3d..46dcaaa 100644
--- a/frontend/src/app/(dashboard)/recent/page.tsx
+++ b/frontend/src/app/(dashboard)/recent/page.tsx
@@ -1,13 +1,9 @@
+"use client";
+
import * as React from "react";
-import type { Metadata } from "next";
import { ContentList } from "@/components/content-list";
import { ContentService } from "@/services/content.service";
-export const metadata: Metadata = {
- title: "Nouveautés | MemeGoat",
- description: "Découvrez les derniers mèmes publiés sur MemeGoat.",
-};
-
export default function RecentPage() {
const fetchFn = React.useCallback((params: { limit: number; offset: number }) =>
ContentService.getRecent(params.limit, params.offset),
diff --git a/frontend/src/app/(dashboard)/trends/page.tsx b/frontend/src/app/(dashboard)/trends/page.tsx
index 1038a9a..6e69a4d 100644
--- a/frontend/src/app/(dashboard)/trends/page.tsx
+++ b/frontend/src/app/(dashboard)/trends/page.tsx
@@ -1,13 +1,9 @@
+"use client";
+
import * as React from "react";
-import type { Metadata } from "next";
import { ContentList } from "@/components/content-list";
import { ContentService } from "@/services/content.service";
-export const metadata: Metadata = {
- title: "Tendances | MemeGoat",
- description: "Découvrez les mèmes les plus populaires du moment sur MemeGoat.",
-};
-
export default function TrendsPage() {
const fetchFn = React.useCallback((params: { limit: number; offset: number }) =>
ContentService.getTrends(params.limit, params.offset),
diff --git a/frontend/src/app/sitemap.ts b/frontend/src/app/sitemap.ts
index 81dfc3f..2ddac0e 100644
--- a/frontend/src/app/sitemap.ts
+++ b/frontend/src/app/sitemap.ts
@@ -6,7 +6,7 @@ export default async function sitemap(): Promise {
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://memegoat.local";
// Pages statiques
- const routes = ["", "/trends", "/recent"].map((route) => ({
+ const routes: MetadataRoute.Sitemap = ["", "/trends", "/recent"].map((route) => ({
url: `${baseUrl}${route}`,
lastModified: new Date(),
changeFrequency: "daily" as const,
diff --git a/frontend/src/components/ui/resizable.tsx b/frontend/src/components/ui/resizable.tsx
deleted file mode 100644
index 12bbd0b..0000000
--- a/frontend/src/components/ui/resizable.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { GripVerticalIcon } from "lucide-react"
-import * as ResizablePrimitive from "react-resizable-panels"
-
-import { cn } from "@/lib/utils"
-
-function ResizablePanelGroup({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function ResizablePanel({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function ResizableHandle({
- withHandle,
- className,
- ...props
-}: React.ComponentProps & {
- withHandle?: boolean
-}) {
- return (
- div]:rotate-90",
- className
- )}
- {...props}
- >
- {withHandle && (
-
-
-
- )}
-
- )
-}
-
-export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 48caa04..2343f98 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -8,4 +8,31 @@ const api = axios.create({
},
});
+// Système anti-spam rudimentaire pour les erreurs répétitives
+const errorCache = new Map();
+const SPAM_THRESHOLD_MS = 2000; // 2 secondes de silence après une erreur sur le même endpoint
+
+api.interceptors.response.use(
+ (response) => {
+ // Nettoyer le cache d'erreur en cas de succès sur cet endpoint
+ const url = response.config.url || "";
+ errorCache.delete(url);
+ return response;
+ },
+ (error) => {
+ const url = error.config?.url || "unknown";
+ const now = Date.now();
+ const lastErrorTime = errorCache.get(url);
+
+ if (lastErrorTime && now - lastErrorTime < SPAM_THRESHOLD_MS) {
+ // Ignorer l'erreur si elle se produit trop rapidement (déjà signalée)
+ // On retourne une promesse qui ne se résout jamais ou on rejette avec une marque spéciale
+ return new Promise(() => {});
+ }
+
+ errorCache.set(url, now);
+ return Promise.reject(error);
+ }
+);
+
export default api;
diff --git a/frontend/src/services/content.service.ts b/frontend/src/services/content.service.ts
index f5f6b0a..2c5c48b 100644
--- a/frontend/src/services/content.service.ts
+++ b/frontend/src/services/content.service.ts
@@ -9,6 +9,7 @@ export const ContentService = {
tag?: string;
category?: string;
query?: string;
+ author?: string;
}): Promise> {
const { data } = await api.get>("/contents/explore", {
params,