본문으로 건너뛰기
cccskills
무료GitHub에 공개

new-page

Scaffold a new frontend page in ayunis-core. Use when adding a new route with its page component following Feature-Sliced Design conventions.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ayunis-core/ayunis-core스타 34(GitHub 34 + cccskills 0)

실행 점수 미검증

설치 방법 보기

포함된 파일(1)

  • SKILL.md8.2 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

New Frontend Page — ayunis-core-frontend

Prerequisites

  • Read the ayunis-core-frontend-dev skill for validation sequence and FSD rules
  • Ensure the dev stack is running (./dev up from repo root)

Working Directory

cd ayunis-core-frontend

Directory Structure

Every page follows this structure:

src/pages/<page-name>/
├── ui/
│   └── <PageName>Page.tsx       # Main page component (default export)
├── api/                          # Optional: mutation hooks
│   ├── useCreate<Entity>.ts
│   ├── useUpdate<Entity>.ts
│   └── useDelete<Entity>.ts
├── model/                        # Optional: types, constants
│   └── openapi.ts               # Re-exported types from generated API
└── index.ts                      # Barrel export

src/app/routes/_authenticated/
└── <page-name>.index.tsx          # TanStack Router route file

File Templates

1. Barrel Export

// src/pages/<page-name>/index.ts
export { default as MyPagePage } from './ui/MyPagePage';

2. Page Component

Pages compose layouts, widgets, and features. They receive data via props (from the route loader) or fetch it internally.

// src/pages/<page-name>/ui/MyPagePage.tsx
import AppLayout from '@/layouts/app-layout';
import ContentAreaLayout from '@/layouts/content-area-layout/ui/ContentAreaLayout';
import ContentAreaHeader from '@/widgets/content-area-header/ui/ContentAreaHeader';
import { useTranslation } from 'react-i18next';

interface MyPagePageProps {
  items: Item[];
}

export default function MyPagePage({ items }: MyPagePageProps) {
  const { t } = useTranslation('<page-name>');

  return (
    <AppLayout>
      <ContentAreaLayout
        contentHeader={
          <ContentAreaHeader title={t('page.title')} />
        }
        contentArea={
          <div>
            {items.map((item) => (
              <div key={item.id}>{item.name}</div>
            ))}
          </div>
        }
      />
    </AppLayout>
  );
}

3. Route File

Routes use TanStack Router's createFileRoute. Data fetching happens in the loader.

// src/app/routes/_authenticated/<page-name>.index.tsx
import { createFileRoute } from '@tanstack/react-router';
import { MyPagePage } from '@/pages/<page-name>';
import {
  getMyEntitiesControllerFindAllQueryKey,
  myEntitiesControllerFindAll,
} from '@/shared/api/generated/ayunisCoreAPI';

export const Route = createFileRoute('/_authenticated/<page-name>/')({
  component: RouteComponent,
  loader: async ({ context: { queryClient } }) => {
    const items = await queryClient.fetchQuery({
      queryKey: getMyEntitiesControllerFindAllQueryKey(),
      queryFn: () => myEntitiesControllerFindAll(),
    });
    return { items };
  },
});

function RouteComponent() {
  const { items } = Route.useLoaderData();
  return <MyPagePage items={items} />;
}

For routes with URL parameters:

// src/app/routes/_authenticated/<page-name>.$id.tsx
import { createFileRoute } from '@tanstack/react-router';
import { MyDetailPage } from '@/pages/<page-name>';
import {
  getMyEntitiesControllerFindOneQueryKey,
  myEntitiesControllerFindOne,
} from '@/shared/api/generated/ayunisCoreAPI';

export const Route = createFileRoute('/_authenticated/<page-name>/$id')({
  component: RouteComponent,
  loader: async ({ params: { id }, context: { queryClient } }) => {
    const item = await queryClient.fetchQuery({
      queryKey: getMyEntitiesControllerFindOneQueryKey(id),
      queryFn: () => myEntitiesControllerFindOne(id),
    });
    return { item };
  },
});

function RouteComponent() {
  const { item } = Route.useLoaderData();
  return <MyDetailPage item={item} />;
}

For routes with search params:

// src/app/routes/_authenticated/<page-name>.tsx
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
import { MyPagePage } from '@/pages/<page-name>';

const searchSchema = z.object({
  filter: z.string().optional(),
});

export const Route = createFileRoute('/_authenticated/<page-name>')({
  component: RouteComponent,
  validateSearch: searchSchema,
});

function RouteComponent() {
  const { filter } = Route.useSearch();
  return <MyPagePage filter={filter} />;
}

4. Mutation Hooks (Optional)

One hook per operation. Each encapsulates a TanStack Query mutation with cache invalidation.

// src/pages/<page-name>/api/useCreateMyEntity.ts
import {
  useMyEntitiesControllerCreate,
  getMyEntitiesControllerFindAllQueryKey,
} from '@/shared/api/generated/ayunisCoreAPI';
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';

export function useCreateMyEntity(onSuccess?: () => void) {
  const queryClient = useQueryClient();
  const { t } = useTranslation('<page-name>');

  const mutation = useMyEntitiesControllerCreate({
    mutation: {
      onSuccess: () => {
        void queryClient.invalidateQueries({
          queryKey: getMyEntitiesControllerFindAllQueryKey(),
        });
        toast.success(t('toast.createSuccess'));
        onSuccess?.();
      },
      onError: () => {
        toast.error(t('toast.createError'));
      },
    },
  });

  return {
    createMyEntity: (data: { name: string }) =>
      mutation.mutate({ data }),
    isCreating: mutation.isPending,
  };
}

5. Model Types (Optional)

Re-export types from the generated API client for cleaner imports within the page module.

// src/pages/<page-name>/model/openapi.ts
export type {
  MyEntityResponseDto as MyEntity,
} from '@/shared/api/generated/ayunisCoreAPI.schemas';

Internationalization

Add translation keys for the new page:

# Create translation files
# src/shared/i18n/locales/de/<page-name>.json
# src/shared/i18n/locales/en/<page-name>.json
{
  "page": {
    "title": "My Page"
  },
  "toast": {
    "createSuccess": "Created successfully",
    "createError": "Failed to create"
  }
}

Register the namespace in src/shared/i18n/i18n.ts.

FSD Import Rules

Pages sit at the top of the dependency hierarchy:

pages → widgets → features → shared

A page can import from:

  • @/widgets/* — Composite UI components
  • @/features/* — Business logic features
  • @/shared/* — Primitives (UI, API, lib, i18n)
  • @/layouts/* — Layout components

A page cannot import from:

  • Other pages (@/pages/*) — pages are siblings, never dependencies
  • @/app/* — the app layer is above pages

Available Layouts

LayoutUse Case
AppLayoutStandard authenticated page with sidebar
ContentAreaLayoutPage with header + scrollable content area
FullScreenMessageLayoutCentered message (empty states, errors)
ChatInterfaceLayoutChat conversation layout with input area

Scaffold Checklist

  1. Create page files:

    • src/pages/<page-name>/ui/<PageName>Page.tsx
    • src/pages/<page-name>/index.ts
    • src/app/routes/_authenticated/<page-name>.index.tsx
  2. Create translations (if using i18n):

    • src/shared/i18n/locales/de/<page-name>.json
    • src/shared/i18n/locales/en/<page-name>.json
    • Register namespace in src/shared/i18n/i18n.ts
  3. Regenerate route tree:

    pnpm exec tsr generate
    

    This updates src/app/routeTree.gen.ts automatically.

  4. Validate:

    pnpm run lint
    pnpm exec tsc --noEmit
    pnpm run build
    
  5. Visual check: Navigate to the new route in the browser and verify it renders.

Reference Pages

Study these existing pages as examples:

  • Simple list page: src/pages/agents/ — list with tabs, create dialog
  • Detail page with params: src/pages/agent/ — single entity view with $id param
  • Search params: src/pages/install/ — page with search schema validation
  • Settings sub-pages: src/pages/settings/ — nested layout with multiple sub-routes
  • Data loading in route: src/app/routes/_authenticated/prompts.index.tsx — loader pattern with query prefetch

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Query Ayunis deployed-environment telemetry in AppSignal — incidents, occurrence counts and error traces via the `appsignal-cli` binary; structured log attributes (user.id, org.id, nestjs.context, custom metadata) via the v2 logs API. Use when investigating a production/staging incident or log data — NOT for local dev logs (see backend-debugging).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ayunis-core/ayunis-core342026년 10월 26일 업데이트

Ayunis Core backend specifics — module boundaries, key files, and project conventions. Complements the nestjs-hexagonal-backend skill.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ayunis-core/ayunis-core342026년 10월 26일 업데이트

Frontend development in ayunis-core. Use when creating, modifying, or debugging frontend code (React, Feature-Sliced Design, API client).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ayunis-core/ayunis-core342026년 10월 26일 업데이트

Debug backend runtime errors (500s, crashes, unexpected behavior). Use when something is broken at runtime — not for writing new code.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ayunis-core/ayunis-core342026년 10월 26일 업데이트

Shared test fixtures for backend specs — port mock factories, domain builders, and shared IDs in a per-module testing/ folder. Use when a module's *.use-case.spec.ts files repeat the same mock-repository literal, inline entity construction, or hardcoded UUIDs across files.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ayunis-core/ayunis-core342026년 10월 26일 업데이트

Analyze Cursor Bugbot PR comments through a root-cause lens: is the finding valid, and is the suggested fix the real fix or a bandaid over a deeper problem? Use when the user asks to check, assess, or triage bugbot comments.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

ayunis-core/ayunis-core342026년 10월 26일 업데이트

ayunis-core의 스킬 모두 보기

이 스킬의 문제 신고