Frontend Developer Skill

SkillDev tools

Use when working on app-partners (partner dashboard) or app-observatory (public statistics). Covers Next.js 15 patterns, DSFR components, API hooks, maps (MapLibre/Deck.gl), charts, and Meilisearch.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Frontend Developer Skill skill

What this skill tells your AI

The instructions your AI receives, as published by covoiturage-gouv-fr/mono in .claude/skills/frontend/SKILL.md and read by ahel’s review.

Read app-partners/README.md or app-observatory/README.md first for setup and configuration.


App Partners — Key Patterns

API Integration

Custom useRestQuery hook:

const { data, loading, error, refetch } = useRestQuery<DataType>(
  "v3",           // API version
  "endpoint/path",
  params,
  { method: "POST", paginate: false },
  [dependencies]
);

Available hooks:

  • useCampaignList(), useCampaignFind()
  • useUsersList(), useOperatorsList(), useTerritoriesList()
  • useExportsList(), useExportCreate(), useExportDownloadLink()
  • useIncentiveGraph(), useOperatorsGraph()

State Management

React Context for authentication, no Redux/Zustand:

const { isAuth, user, logout } = useAuth();

DSFR Components

import { Button, Input, Select, Table, Alert } from "@codegouvfr/react-dsfr";
import { fr } from "@codegouvfr/react-dsfr";

className={fr.cx("fr-container", "fr-mb-7w")}

Key components: Header, Footer, Button, Input, Select, Table, Pagination, Alert, Badge, Download.

Form Validation (Zod)

import { z } from "zod";

const schema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
});

Input error state:

<Input
  state={errors.email ? "error" : "default"}
  stateRelatedMessage={errors.email}
/>

Modal Forms

useActionsModal hook for CRUD operations:

const modal = useActionsModal<DataType>();
// modal.setCurrentRow(), modal.setOpenModal(), modal.setTypeModal()

Authentication

ProConnect SSO flow:

  1. Login via ProConnectButton -> ProConnect URL
  2. Session check on load: GET /auth/me
  3. Role-based access control

Role helpers:

isRegistry(), isTerritory(), isOperator(), isAdmin(), isUser()

Analytics

import { sendEvent } from "@socialgouv/matomo-next";
void sendEvent({ category: "campagne", action: "Consultation" });

Conventions

  • Components: PascalCase (AuthButton.tsx)
  • Hooks: camelCase with use prefix (useCampaignList.ts)
  • Interfaces: PascalCase with Interface suffix
  • Client components: Add "use client" directive
  • Pre-build hook: react-dsfr update-icons

App Observatory — Key Patterns

Map Integration

import Map from "react-map-gl/maplibre";
import DeckGL from "@deck.gl/react";

Deck.gl layers:

  • H3HexagonLayer - Density visualization with hexagonal bins
  • ArcLayer - Origin-destination flow visualization

H3 Hexagonal Binning

import { cellToBoundary, cellToLatLng } from "h3-js";
const h3Index = latLngToCell(lat, lng, 9); // Resolution 9 for journey data

Data Visualization

Chart.js with react-chartjs-2:

import { Line } from "react-chartjs-2";
import { Chart, CategoryScale, LinearScale, PointElement, LineElement } from "chart.js";
Chart.register(CategoryScale, LinearScale, PointElement, LineElement);

API Data Fetching

const { data, loading, error } = useApi<DataType>(url, params);
const jsonData = useJson<DataType>(url);

Key endpoints:

EndpointPurpose
/observatory/journeysJourney statistics
/observatory/directionsDirection analysis
/observatory/odOrigin-destination matrices
/observatory/territoriesTerritory data

Meilisearch Integration

import { MeiliSearch } from "meilisearch";

const client = new MeiliSearch({ host: MEILISEARCH_URL });
const results = await client.multiSearch({
  queries: [
    { indexUid: "article", q: query },
    { indexUid: "resource", q: query },
    { indexUid: "page", q: query },
  ],
});

MDX Content

import { MDXRemote } from "next-mdx-remote/rsc";
import remarkGfm from "remark-gfm";
import rehypeSlug from "rehype-slug";

<MDXRemote
  source={content}
  options={{ mdxOptions: { remarkPlugins: [remarkGfm], rehypePlugins: [rehypeSlug] }}}
/>

Content fetched from Strapi CMS with 60s revalidation.

Geographic Data

type PerimeterType = "country" | "region" | "department" | "aom" | "epci" | "commune";
type INSEECode = string; // 5-digit commune code

Jenks natural breaks for choropleth maps:

import { jenks } from "simple-statistics";
const breaks = jenks(values, 5);

Conventions

  • Suspense boundaries for async data
  • Server/client component split
  • Container/presentational pattern
  • Strong TypeScript typing for geographic data
  • Pre-build hook: only-include-used-icons

Signals

GitHub stars
36
Forks
12
Last commit
Sep 2026
Hacker News mentions
20
Advanced
Catalog kind
skill
Gateway key
frontend-covoiturage-gouv-fr
Source
github.com/covoiturage-gouv-fr/mono