Gå til innholdet

page-component-constraints

Håndhev at sidekomponenter er tynne layout-innpakninger — små i størrelse og fri for datahentingslogikk.

I frontend-arkitekturer som skiller ruting fra logikk, bør sidekomponenter bare sette sammen layout og delegere datahenting til Connected-komponenter. Denne regelen håndhever to begrensninger i en enkelt regelfil:

  1. Størrelsesgrense — Sidekomponenter må holde seg under et konfigurerbart linjetall (standard: 75 linjer).
  2. Ingen data-hooks — Sidekomponenter må ikke bruke useState, useQuery, useMutation eller andre datahentings-hooks direkte.
src/pages/DashboardPage.tsx
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
export default function DashboardPage() {
const [filter, setFilter] = useState(""); // ✗ state hook
const { data } = useQuery({ queryKey: ["dashboard"] }); // ✗ data hook
// ... 120 lines of layout + logic
}
src/pages/DashboardPage.tsx
import { DashboardConnected } from "../components/DashboardConnected";
import { Sidebar } from "../components/Sidebar";
export default function DashboardPage() {
return (
<div className="flex">
<Sidebar />
<DashboardConnected />
</div>
);
}
/// <reference path="../rules.d.ts" />
const PAGE_MAX_LINES = 75;
export default {
rules: {
"page-max-lines": {
description: `Page components must be under ${PAGE_MAX_LINES} lines`,
async check(ctx) {
const pageFiles = await ctx.glob("src/pages/*Page.tsx");
for (const file of pageFiles) {
if (file.includes(".test.")) continue;
const content = await ctx.readFile(file);
const lineCount = content.split("\n").length;
if (lineCount > PAGE_MAX_LINES) {
ctx.report.violation({
message: `Page component has ${lineCount} lines (max ${PAGE_MAX_LINES}). Extract logic to Connected components.`,
file,
fix: "Move data-fetching and business logic to Connected components",
});
}
}
},
},
"page-no-data-hooks": {
description: "Page components must not use data-fetching or state hooks",
async check(ctx) {
const pageFiles = await ctx.glob("src/pages/*Page.tsx");
const FORBIDDEN_HOOKS =
/\b(useState|useForm|useQuery|useMutation|useSuspenseQuery|useInfiniteQuery)\s*[<(]/g;
const ALLOWED_HOOKS = new Set([
"useParams",
"useNavigate",
"useRouter",
"useMatch",
"useLocation",
"useSearch",
]);
for (const file of pageFiles) {
if (file.includes(".test.")) continue;
const content = await ctx.readFile(file);
let match;
while ((match = FORBIDDEN_HOOKS.exec(content)) !== null) {
ctx.report.violation({
message: `Page component uses "${match[1]}" hook. Extract to a Connected component.`,
file,
fix: `Move the "${match[1]}" hook to a Connected component`,
});
}
}
},
},
},
} satisfies RuleSet;

Når frontend-arkitekturen din følger page/container/presentational-mønsteret og du vil håndheve at sider forblir tynne rutingsendepunkter. Juster PAGE_MAX_LINES og hook-listene for ditt rammeverk.

Når sider forventes å inneholde logikk (f.eks. i Next.js server-komponenter der datahenting i siden er idiomatisk), eller når prosjektet ditt ikke følger dette arkitekturmønsteret.