Skip to content

refactor(website): split [id].astro into CategoryPage and SkillDetailPage components #1380

Description

@hytonylee

Context

skills/[id].astro is 913 lines handling two completely unrelated page types in one file — category landing pages (SSR, static content) and skill detail pages (client-side fetch). This is the SMI-3018 route-collision workaround. The file also has a 350-line is:inline script block that can be extracted once the React hooks from #1378 exist.

Depends on: #1376 (types + shared utilities), #1378 (hooks)

Work

src/pages/skills/_components/CategoryPage.astro
Receives categoryMeta: CategoryMeta and categorySkills: SkillItem[] as props. Contains all the category landing page HTML currently at [id].astro:199–297 (~100 lines of markup + breadcrumb + CTA + skill grid + about section).

src/pages/skills/_components/SkillDetailPage.tsx
React component (client island) for the skill detail view. Replaces the 350-line is:inline script + the static HTML shell at [id].astro:299–561. Uses useAuth from #1378, calls skills-get directly, renders via typed DOM updates → proper JSX instead.

Key sections:

  • Breadcrumb (dynamic name after fetch)
  • Loading / error states
  • Skill header (name, author, trust badge, quality tier)
  • Description block
  • README section (shown when skill.content present)
  • Installation commands × 3 (npx / CLI / assistant) with copy buttons
  • Details + Tags grid
  • "Users also installed" + Related skills

skills/[id].astro after:

---
import BaseLayout from '../../layouts/BaseLayout.astro'
import CategoryPage from './_components/CategoryPage.astro'
import { SkillDetailPage } from './_components/SkillDetailPage'
import { CATEGORIES } from '../../data/categories'

const { id } = Astro.params
const categoryMeta = CATEGORIES.find(c => c.slug === id)
// ... SSR fetch for category skills (unchanged logic, ~15 lines)
---
{categoryMeta
  ? <CategoryPage categoryMeta={categoryMeta} categorySkills={categorySkills} />
  : <SkillDetailPage client:load skillId={id} apiBase={apiBase} />
}

Target: [id].astro ≤ 80 lines.

Acceptance criteria

  • [id].astro is under 500 lines (audit:standards passes)
  • Category landing pages render identically (SSR, JSON-LD, breadcrumb, skill grid, CTA)
  • Skill detail pages load and render all sections correctly
  • Copy buttons work on all three install commands
  • "View source" link shows/hides based on repo_url
  • also_installed and related_skills sections appear when data is present
  • npm run typecheck + npm run lint pass
  • Edge cache headers (s-maxage=300) still set for category pages

Metadata

Metadata

Assignees

No one assigned

    Labels

    frontendUI, styling, webrefactorImprove code without changing behavior

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions