État URL (Nuqs)
Tri, filtres, pagination et état des colonnes dans l'URL avec Nuqs
État URL (Nuqs)
La table conserve tri, filtres, pagination, visibilité des colonnes, mode d'affichage, groupement et pinning dans l'URL avec Nuqs. Cela apporte :
- Liens partageables – Envoyez un lien et le destinataire voit la même vue (même tri, filtres, page).
- Retour/avance navigateur – L'historique reflète l'état de la table.
- Compatible SSR – La même URL peut être utilisée pour le rendu côté serveur ou le prefetch.
Setup (Next.js App Router)
- Installez nuqs :
npm install nuqs- Ajoutez NuqsAdapter dans votre layout racine :
// app/layout.tsx
import { NuqsAdapter } from "nuqs/adapters/next/app";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<NuqsAdapter>
{children}
</NuqsAdapter>
</body>
</html>
);
}Aucune autre configuration n'est nécessaire ; la table utilise Nuqs en interne.
Ce qui est stocké dans l'URL
Les query params sont préfixés par table id (votre tableType, par ex. products). Noms typiques :
| Param | Exemple | Description |
|---|---|---|
{tableId}-sort | products-sort | État du tri (tableau de { id, desc }). |
{tableId}-filters | products-filters | Filtres de colonnes. |
{tableId}-advancedFilters | products-advancedFilters | Règles de filtres avancés. |
{tableId}-q | products-q | Recherche globale. |
{tableId}-page | products-page | Index de page en base 0. |
{tableId}-pageSize | products-pageSize | Taille de page. |
{tableId}-visibility | products-visibility | Visibilité des colonnes (objet). |
{tableId}-order | products-order | Ordre des colonnes. |
{tableId}-display | products-display | Mode d'affichage (table, kanban ou gallery). |
{tableId}-kanban | products-kanban | Overrides Kanban : lanes, colonne titre, propriétés de carte et libellés. |
{tableId}-kanbanGroupBy | products-kanbanGroupBy | Paramètre legacy de colonne de lanes Kanban. Encore lu en fallback si {tableId}-kanban.groupBy est absent. |
{tableId}-gallery | products-gallery | Overrides Gallery : image, titre, propriétés, ratio média, fit, taille et libellés. |
{tableId}-grouping | products-grouping | IDs des colonnes de regroupement. |
{tableId}-pinning | products-pinning | Colonnes épinglées (gauche/droite). |
Les valeurs sont encodées en JSON ou en string simple. La table les lit et les écrit via Nuqs ; vous n'avez pas besoin de les lire vous-même sauf si vous voulez les exploiter côté serveur (par ex. pour SSR).
Vues enregistrées
Les vues enregistrées sont construites depuis le même état basé sur l'URL. Le gestionnaire de vues est activé par défaut et peut être désactivé avec enableViews: false. Utilisez allowViewSave: false quand les utilisateurs peuvent sélectionner des vues existantes mais ne doivent pas créer, mettre à jour ou supprimer des vues. Utilisez allowViewSharing: true pour afficher l'option “Partager avec l'équipe” au moment d'enregistrer une vue.
Quand une vue est enregistrée, Yayaw Table persiste ce snapshot adapté à la base de données :
- Recherche globale (
{tableId}-q) - Filtres de colonnes et filtres avancés
- Tri
- Visibilité et ordre des colonnes
- Mode d'affichage, overrides Kanban et overrides Gallery
- Groupement
- Pinning des colonnes
- Taille de page
La page courante, les lignes dépliées et l'index d'historique navigateur ne sont pas persistés. Appliquer une vue remet toujours {tableId}-page à 0, pour éviter qu'une vue filtrée ne s'ouvre sur une page hors limites.
Les vues Kanban et Gallery persistent uniquement les choix d'affichage. Kanban stocke les colonnes lane/titre/propriétés et l'affichage des libellés; Gallery stocke les colonnes image/titre/propriétés et les réglages de layout des cartes. Aucune vue ne stocke les données de lignes ni les données images.
Pour la persistance en production, exposez les actions de vues depuis getTableActions :
const getTableActions = (tableType: string) => {
if (tableType !== "products") {
return;
}
return {
list: listProducts,
views: {
list: async ({ tableId }) => ({ data: await db.views.list(tableId) }),
create: async (input) => await db.views.create(input), // input.isGlobal vaut true pour les vues partagées avec l'équipe
update: async (id, input) => await db.views.update(id, input),
delete: async (id, context) => await db.views.delete(id, context),
},
};
};Si aucune action views n'est fournie, le composant copié utilise un fallback localStorage pour garder l'UI utilisable dans les prototypes. Les applications consommatrices doivent remplacer ce fallback par des actions adossées à la base quand les vues enregistrées doivent suivre les utilisateurs ou les équipes authentifiés.
Modèle de données recommandé
Utilisez une table générique table_views pour toutes les instances Yayaw Table plutôt qu'une table de vues par entité métier. Une vue enregistrée décrit un état UI, pas une table SQL; identifiez donc la table cible avec une clé applicative stable comme products, customers ou orders. Évitez d'utiliser un nom de table SQL comme identifiant long terme, car une table UI peut être alimentée par des joins, des index de recherche, des réponses API ou des tables renommées.
create table table_views (
id uuid primary key,
workspace_id uuid not null,
table_key text not null,
name text not null,
config jsonb not null,
visibility text not null default 'private',
owner_user_id uuid,
created_by_id uuid,
is_system boolean not null default false,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
deleted_at timestamptz
);Stockez la config normalisée de la vue dans config plutôt que la chaîne URL complète. Garder le snapshot structuré facilite la validation, les migrations, l'inspection et l'application sur plusieurs clients.
{
"version": 1,
"sorting": [],
"columnFilters": [],
"advancedFilters": [],
"globalSearch": "samsung",
"columnVisibility": {},
"columnOrder": [],
"displayMode": "gallery",
"kanban": {
"groupBy": "status",
"titleColumn": "name",
"cardColumnIds": ["brand", "price"],
"showCardLabels": false
},
"gallery": {
"imageColumn": "imageUrl",
"titleColumn": "name",
"cardColumnIds": ["brand", "category", "price", "status"],
"aspectRatio": "square",
"imageFit": "cover",
"cardSize": "medium"
},
"columnPinning": { "left": [], "right": [] },
"grouping": [],
"pageSize": 50
}Utilisez visibility pour représenter si une vue est privée, partagée avec un workspace ou fournie par le système. Si les utilisateurs peuvent choisir leur propre vue par défaut, gardez cette préférence hors de table_views pour que le défaut reste non ambigu :
create table table_view_preferences (
user_id uuid not null,
workspace_id uuid not null,
table_key text not null,
default_view_id uuid references table_views(id),
primary key (user_id, workspace_id, table_key)
);Cela permet à une même vue partagée d'être la vue par défaut d'un utilisateur sans devenir la vue par défaut de tout le monde. Pour des défauts workspace-wide, stockez cela séparément comme préférence workspace ou ajoutez une colonne scope explicite à la table de préférences.
Partage et reset
- Copier le lien – La barre d'outils peut proposer “Copy link” / “Share” avec l'URL courante (tous les paramètres table y sont déjà).
- Reset – Réinitialiser l'état de la table efface ces paramètres (ou restaure les valeurs par défaut), donc l'URL est mise à jour en conséquence.
Usage server-side de l'état URL
Si vous rendez la table côté serveur (par ex. dans un Server Component), vous pouvez lire les mêmes paramètres depuis searchParams et passer des données initiales ou les utiliser dans votre Server Action. Le client hydratera avec la même URL et Nuqs restera synchronisé.
Dépendances
- nuqs – Requis pour l'état URL. La table utilise
useQueryStateet des parseurs custom. - Next.js – Utilisez
nuqs/adapters/next/apppour l'App Router.
Voir aussi :