Frontend Developer Skill
SkillDev toolsUse 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.
No other account needed.
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.mdorapp-observatory/README.mdfirst 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:
- Login via
ProConnectButton-> ProConnect URL - Session check on load:
GET /auth/me - 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
useprefix (useCampaignList.ts) - Interfaces: PascalCase with
Interfacesuffix - 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 binsArcLayer- 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:
| Endpoint | Purpose |
|---|---|
/observatory/journeys | Journey statistics |
/observatory/directions | Direction analysis |
/observatory/od | Origin-destination matrices |
/observatory/territories | Territory 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