Receber por email
← Voltar para o catálogo

senior-frontend

Next.js App Router specific patterns — Server Components, Client Components boundary, parallel fetching, bundle analysis, a11y. Use ONLY for Next.js 13+ App Router projects. For generic React/Vue patterns, use `frontend-patterns` instead.

67estrelas
Atualizado há 5 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add tranhieutt/software_development_department

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/senior-frontend-tranhieutt/svg)](https://www.skillteca.com.br/skills/senior-frontend-tranhieutt)

Alerta por categoria

Receba novas skills de Design e Frontend toda segunda

1 email curto, só com as skills novas de Design e Frontend. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Senior Frontend

Critical rules (non-obvious)

  • Always return in server components before client boundary — mixing async server + client state without boundaries causes hydration mismatches
  • priority on LCP images only — adding priority everywhere defeats preload budgets
  • use client at the leaf, not the root — push client boundary as deep as possible to maximize RSC tree
  • Parallel data fetching in Server Components: use Promise.all([...]) at the page level, not sequential awaits
  • Bundle heavy deps: moment (290KB) → dayjs (2KB); lodash → lodash-es with tree-shaking; axios → native fetch

Next.js: server vs client boundary

// Server Component (default) — fetch directly, no hooks
async function ProductPage({ params }: { params: { id: string } }) {
  const [product, reviews] = await Promise.all([  // parallel fetch
    getProduct(params.id),
    getReviews(params.id),
  ]);
  return (
    <div>
      <h1>{product.name}</h1>
      <Suspense fallback={<ReviewsSkeleton />}>
        <Reviews productId={params.id} />  {/* can defer slow queries */}
      </Suspense>
      <AddToCartButton productId={product.id} />  {/* client boundary at leaf */}
    </div>
  );
}

// Client Component — only where interactivity needed
"use client";
function AddToCartButton({ productId }: { productId: string }) {
  const [adding, setAdding] = useState(false);
  return <button onClick={() => addToCart(productId)}>Add to Cart</button>;
}

Next.js: config essentials

// next.config.js
const nextConfig = {
  images: {
    remotePatterns: [{ hostname: "cdn.example.com" }],
    formats: ["image/avif", "image/webp"],
  },
  experimental: {
    optimizePackageImports: ["lucide-react", "@heroicons/react"],  // tree-shake icon libs
  },
};

Component: TypeScript patterns

// Generic list component
function List<T extends { id: string }>({ items, renderItem }: {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}) {
  return <ul>{items.map(item => <li key={item.id}>{renderItem(item)}</li>)}</ul>;
}

// Props extending HTML element
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: "primary" | "ghost" | "danger";
  isLoading?: boolean;
}

export function Button({ variant = "primary", isLoading, children, ...props }: ButtonProps) {
  return (
    <button {...props} disabled={props.disabled || isLoading} aria-busy={isLoading}
      className={cn("px-4 py-2 rounded font-medium focus-visible:ring-2",
        variant === "primary" && "bg-blue-600 text-white hover:bg-blue-700",
        variant === "danger" && "bg-red-600 text-white",
        (props.disabled || isLoading) && "opacity-50 cursor-not-allowed"
      )}>
      {isLoading && <Spinner aria-hidden />}
      {children}
    </button>
  );
}

Performance: bundle analysis

Common heavy deps to replace:

| Package | Size | Alternative | |---|---|---| | moment | 290KB | dayjs (2KB) or date-fns (12KB) | | lodash | 71KB | lodash-es (tree-shakeable) | | axios | 14KB | native fetch or ky (3KB) | | @mui/material | Large | shadcn/ui or Radix UI |

# Analyze bundle
npx @next/bundle-analyzer  # or
npx vite-bundle-visualizer

Accessibility essentials

// Skip link — place before main nav
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4">
  Skip to main content
</a>

// Icon button — always label
<button type="button" aria-label="Close dialog" className="focus-visible:ring-2">
  <XIcon aria-hidden="true" />
</button>

// Minimum contrast: 4.5:1 for text, 3:1 for UI components

Project structure (Next.js App Router)

app/
├── layout.tsx          # Root layout: fonts, providers, metadata
├── page.tsx
├── (auth)/             # Route group — no URL segment
│   ├── login/page.tsx
│   └── register/page.tsx
└── api/
    └── [route]/route.ts
components/
├── ui/                 # Button, Input, Card (reusable primitives)
└── features/           # Domain-specific composites
hooks/                  # useDebounce, useLocalStorage, useMediaQuery
lib/
├── utils.ts            # cn(), formatDate()
└── api.ts              # API client
types/                  # Shared TypeScript types
Compartilhar X LinkedIn