Back to skills
SKILL.md
Tpl Frontend Remix Typescript
ASecurityTemplate do pack (frontend/09-remix-typescript.md). Orienta o agente em interfaces, componentes e apps de frontend alinhado a esse contexto.
- 11 stars
- 0 votes
- 0 copies
- 4 views
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add artubss/SKILLS-CLAUDE-CODE --skill tpl-frontend-remix-typescript --agent claude-codeAre you the author of Tpl Frontend Remix Typescript?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/artubss-tpl-frontend-remix-typescript)---
name: tpl-frontend-remix-typescript
description: Template do pack (frontend/09-remix-typescript.md). Orienta o agente em interfaces, componentes e apps de frontend alinhado a esse contexto.
metadata:
version: 1.0.0
source_template: frontend/09-remix-typescript.md
generated_by: install_pack_templates_as_claude_skills
---
# PROJECT: [Nome do App]
Skill gerado a partir do pack `templates-claude-code`. Arquivo de origem: `frontend/09-remix-typescript.md`. Use como baseline e adapte ao projeto antes de mudancas grandes.
## Conteudo do template
> CLAUDE.md — Remix v2 + TypeScript
> Gerado pelo Pack CLAUDE.md Elite
---
## STACK
| Camada | Tecnologia | Versão |
|--------|-----------|--------|
| Framework | Remix | v2.x |
| Linguagem | TypeScript | 5.x (strict) |
| ORM | Prisma | 5.x |
| Banco de Dados | PostgreSQL | 16+ |
| Estilização | Tailwind CSS | v4 |
| Validação | Zod | 3.x |
| Testes | Vitest + Testing Library | latest |
| Runtime | Node.js | 20 LTS |
| Deploy | Fly.io / Railway / Vercel | — |
---
## PROJECT STRUCTURE
```
app/
├── routes/ # File-based routing (Remix v2)
│ ├── _index.tsx # / (home)
│ ├── _auth.tsx # layout route sem caminho
│ ├── _auth.login.tsx # /login
│ ├── _auth.register.tsx # /register
│ ├── dashboard.tsx # /dashboard (layout)
│ ├── dashboard._index.tsx # /dashboard (index)
│ ├── dashboard.projects.tsx # /dashboard/projects
│ ├── dashboard.projects.$id.tsx
│ └── api.webhook.ts # /api/webhook (resource route)
├── components/
│ ├── ui/ # Componentes primitivos (Button, Input, Modal)
│ └── features/ # Componentes de domínio
├── lib/
│ ├── db.server.ts # Instância do Prisma (server-only)
│ ├── auth.server.ts # Sessão e autenticação
│ ├── validations/ # Schemas Zod reutilizáveis
│ └── utils.ts # Helpers puros (sem server imports)
├── hooks/ # Custom React hooks (client)
├── types/ # TypeScript types/interfaces globais
├── styles/
│ └── tailwind.css
├── entry.client.tsx
├── entry.server.tsx
└── root.tsx
prisma/
├── schema.prisma
└── migrations/
public/
tests/
├── unit/
├── integration/
└── e2e/
```
**Regras de nomenclatura de arquivos:**
- Arquivos `*.server.ts` NUNCA são importados no cliente — Remix tree-shakes automaticamente.
- Layout routes usam `_prefix` no nome do arquivo.
- Resource routes (sem UI) exportam apenas `loader` ou `action`, sem `default`.
---
## MENTAL MODEL — LOADER / ACTION
### Loader (GET — leitura de dados)
```typescript
// app/routes/dashboard.projects.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { requireUser } from "~/lib/auth.server";
import { db } from "~/lib/db.server";
export async function loader({ request }: LoaderFunctionArgs) {
const user = await requireUser(request); // lança redirect se não autenticado
const projects = await db.project.findMany({
where: { userId: user.id },
orderBy: { createdAt: "desc" },
});
return json({ projects, user });
}
export default function ProjectsPage() {
const { projects } = useLoaderData<typeof loader>();
return (/* JSX */);
}
```
### Action (POST/PUT/DELETE — mutações)
```typescript
// app/routes/dashboard.projects.tsx
import { redirect, type ActionFunctionArgs } from "@remix-run/node";
import { z } from "zod";
const CreateProjectSchema = z.object({
name: z.string().min(1).max(100),
description: z.string().optional(),
});
export async function action({ request }: ActionFunctionArgs) {
const user = await requireUser(request);
const formData = await request.formData();
const result = CreateProjectSchema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return json(
{ errors: result.error.flatten().fieldErrors },
{ status: 400 }
);
}
await db.project.create({
data: { ...result.data, userId: user.id },
});
return redirect("/dashboard/projects");
}
```
### Regras de Loader/Action
1. **Loaders são apenas leitura** — nunca mutam dados.
2. **Actions sempre retornam `json()` com erros OU `redirect()`** — nunca retornam dados de sucesso inline (use redirect + flash message).
3. **Validar TODOS os inputs** com Zod antes de tocar no banco.
4. **`requireUser(request)`** deve ser a primeira chamada em qualquer loader/action protegido.
5. **Nunca usar `throw new Response()`** para erros de negócio — reservar para 404/401/403.
---
## NESTED ROUTING
```
root.tsx ← providers, meta global, outlet
└── _auth.tsx ← layout: navbar de auth, sem URL própria
├── _auth.login.tsx ← /login
└── _auth.register.tsx ← /register
└── dashboard.tsx ← layout: sidebar, header autenticado
├── dashboard._index.tsx ← /dashboard
├── dashboard.projects.tsx ← /dashboard/projects
└── dashboard.projects.$id.tsx ← /dashboard/projects/:id
```
**Regras de Nested Routing:**
- `<Outlet />` deve aparecer **exatamente uma vez** em cada layout route.
- `useMatches()` para breadcrumbs — não recalcular em cada componente.
- `shouldRevalidate` para evitar re-fetches desnecessários após actions que não afetam o route pai.
- Nunca colocar lógica de negócio em `root.tsx` — apenas providers e ErrorBoundary global.
---
## ERROR BOUNDARIES
```typescript
// Sempre em layout routes críticos
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} {error.statusText}</h1>
<p>{error.data}</p>
</div>
);
}
// Erro inesperado
return (
<div>
<h1>Algo deu errado</h1>
<p>Por favor, recarregue a página.</p>
</div>
);
}
```
**Regras:**
- `ErrorBoundary` obrigatório em `root.tsx`.
- `ErrorBoundary` recomendado em qualquer route que faça chamadas externas.
- Nunca expor stack trace em produção — logar no servidor, mostrar mensagem genérica.
---
## AUTENTICAÇÃO
```typescript
// lib/auth.server.ts
import { createCookieSessionStorage, redirect } from "@remix-run/node";
const sessionStorage = createCookieSessionStorage({
cookie: {
name: "__session",
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
secrets: [process.env.SESSION_SECRET!],
maxAge: 60 * 60 * 24 * 30, // 30 dias
},
});
export async function requireUser(request: Request) {
const session = await sessionStorage.getSession(request.headers.get("Cookie"));
const userId = session.get("userId");
if (!userId) throw redirect("/login");
const user = await db.user.findUnique({ where: { id: userId } });
if (!user) throw redirect("/login");
return user;
}
```
---
## VALIDAÇÃO COM ZOD
```typescript
// lib/validations/project.ts
import { z } from "zod";
export const ProjectSchema = z.object({
name: z.string().min(1, "Nome obrigatório").max(100, "Máximo 100 caracteres"),
description: z.string().max(500).optional(),
status: z.enum(["draft", "active", "archived"]).default("draft"),
});
export type ProjectInput = z.infer<typeof ProjectSchema>;
// Em action:
const result = ProjectSchema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return json({ errors: result.error.flatten().fieldErrors }, { status: 422 });
}
```
---
## ROUTING TABLE
| Trigger | Método | Route File | Handler | Descrição |
|---------|--------|-----------|---------|-----------|
| Acessa `/` | GET | `_index.tsx` | `loader` | Landing page |
| Acessa `/login` | GET | `_auth.login.tsx` | `loader` | Página de login |
| Submete form login | POST | `_auth.login.tsx` | `action` | Autentica usuário |
| Acessa `/dashboard` | GET | `dashboard._index.tsx` | `loader` | Dashboard home, requer auth |
| Acessa `/dashboard/projects` | GET | `dashboard.projects.tsx` | `loader` | Lista projetos |
| Cria projeto | POST | `dashboard.projects.tsx` | `action` | Cria projeto via form |
| Acessa `/dashboard/projects/:id` | GET | `dashboard.projects.$id.tsx` | `loader` | Detalhes do projeto |
| Edita projeto | POST | `dashboard.projects.$id.tsx` | `action` | Atualiza projeto (method=PUT via hidden input) |
| Deleta projeto | POST | `dashboard.projects.$id.tsx` | `action` | Deleta (method=DELETE via hidden input) |
| Webhook externo | POST | `api.webhook.ts` | `action` | Resource route, sem UI |
---
## FORMULÁRIOS
```tsx
// Sempre usar <Form> do Remix, não <form> nativo
import { Form, useActionData, useNavigation } from "@remix-run/react";
export default function CreateProject() {
const actionData = useActionData<typeof action>();
const navigation = useNavigation();
const isSubmitting = navigation.state === "submitting";
return (
<Form method="post">
<input name="name" />
{actionData?.errors?.name && (
<p className="text-red-500">{actionData.errors.name[0]}</p>
)}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Salvando..." : "Criar Projeto"}
</button>
</Form>
);
}
```
---
## PRISMA — PADRÕES
```typescript
// lib/db.server.ts — singleton para dev (evita conexões duplicadas no HMR)
import { PrismaClient } from "@prisma/client";
declare global {
var __db__: PrismaClient | undefined;
}
export const db =
global.__db__ ||
new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["query", "error"] : ["error"],
});
if (process.env.NODE_ENV !== "production") {
global.__db__ = db;
}
```
**Regras Prisma:**
- Nunca importar `db` em arquivos sem `.server.ts` no nome.
- Usar `select` explícito — nunca retornar objetos Prisma completos para o cliente.
- Migrations: `npx prisma migrate dev --name <descricao>` sempre com nome descritivo.
- Seeds em `prisma/seed.ts`.
---
## TESTES
```typescript
// tests/unit/validations.test.ts
import { describe, it, expect } from "vitest";
import { ProjectSchema } from "~/lib/validations/project";
describe("ProjectSchema", () => {
it("aceita input válido", () => {
const result = ProjectSchema.safeParse({ name: "Meu Projeto" });
expect(result.success).toBe(true);
});
it("rejeita nome vazio", () => {
const result = ProjectSchema.safeParse({ name: "" });
expect(result.success).toBe(false);
});
});
```
```typescript
// tests/integration/loader.test.ts
import { createRequest } from "~/test-utils/request";
import { loader } from "~/routes/dashboard.projects";
describe("ProjectsLoader", () => {
it("redireciona para /login se não autenticado", async () => {
const request = createRequest("/dashboard/projects");
await expect(loader({ request, params: {}, context: {} })).rejects.toThrow();
});
});
```
---
## QUALITY GATES
Antes de cada commit, verificar:
- [ ] `npx tsc --noEmit` — sem erros TypeScript
- [ ] `npx eslint . --max-warnings 0` — sem warnings
- [ ] `npx vitest run` — todos os testes passando
- [ ] Toda action valida input com Zod antes de tocar no banco
- [ ] Toda route protegida chama `requireUser()` como primeira linha do loader/action
- [ ] Arquivos `.server.ts` não importam de arquivos sem `.server.ts` que importem browser APIs
- [ ] Nenhuma query N+1 — usar `include` ou `select` com relações aninhadas
- [ ] `ErrorBoundary` presente em todos os layout routes
- [ ] Variáveis de ambiente acessadas via `process.env.VAR_NAME!` ou validadas no startup
- [ ] Nenhum `console.log` em produção — usar logger estruturado
---
## FORBIDDEN
```
❌ NUNCA importar "~/lib/db.server" em arquivos sem `.server.ts`
❌ NUNCA usar `<form>` nativo para mutations — usar `<Form>` do Remix
❌ NUNCA colocar lógica de negócio em componentes — mover para loader/action
❌ NUNCA retornar dados sensíveis (senha hash, tokens) via `json()` para o cliente
❌ NUNCA usar `useEffect` para buscar dados — isso é trabalho do loader
❌ NUNCA criar estado local para dados que deveriam estar no servidor
❌ NUNCA chamar `fetch()` no cliente para dados da app — usar loaders
❌ NUNCA usar `any` em TypeScript — sempre tipar explicitamente
❌ NUNCA commit sem rodar os quality gates
❌ NUNCA armazenar SESSION_SECRET hardcoded — apenas via env
```
---
## ENV VARIABLES
```bash
# .env.example
DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
SESSION_SECRET="super-secret-key-min-32-chars"
NODE_ENV="development"
# Adicionar novos vars aqui e atualizar .env.example
```
---
## COMMANDS
```bash
# Dev
npm run dev
# Banco de dados
npx prisma migrate dev --name <nome>
npx prisma studio
npx prisma generate
# Build e typecheck
npm run build
npx tsc --noEmit
# Testes
npx vitest run
npx vitest --ui
# Lint
npx eslint . --fix
```
Attribution
Related skills
View all in databases →Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.