Skip to main content

Summary

The “New Project” button on the Sparki dashboard navigated to /dashboard/projects/new, but that URL was caught by the [id] dynamic route segment, which treated "new" as a project ID. The API returned HTML (404 page) instead of JSON, causing the "Unexpected token '<', '<!DOCTYPE'..." parse error. Additionally, the “New Project” button on the projects list page (/dashboard/projects) was a dead <button> with no click handler or link.

Root cause

1. Missing static route for /projects/new

The file system routing structure was:
/dashboard/projects/
  page.tsx           # projects list
  [id]/page.tsx      # project detail — catches ANY slug, including "new"
In Next.js App Router, a [id] dynamic segment matches every path segment. Without a static new/ directory, "new" is treated as a project ID. The useProject("new") hook fires, getProject("new") calls GET /api/projects/new, the server returns its HTML fallback, and response.json() throws the parse error.

2. Unwired button on projects list page

/dashboard/projects/page.tsx line 132 rendered a <button> element with the correct visual styling but zero interactivity — no href, no onClick, no Link wrapper. The dashboard page (/dashboard/page.tsx line 268) correctly used <Link href="/dashboard/projects/new">, but the projects page didn’t match.

Fix applied

Static route created

/dashboard/projects/
  page.tsx           # projects list
  new/page.tsx       # NEW — create project form (static, takes precedence over [id])
  [id]/page.tsx      # project detail
The new page uses the existing ProjectSetupWizard component (from @/components/forms/Wizards) and useCreateProject hook (from @/hooks). On successful creation, it redirects to /dashboard/projects/{id}. On cancel, it returns to the projects list. The <button> on the projects list page was replaced with <Link href="/dashboard/projects/new">, keeping the same visual styling.

Key takeaways

  1. Static routes before dynamic routes — In Next.js App Router, always create explicit static route directories for known path segments (new, edit, settings) before relying on [param] catch-alls. This is a common footgun when adding CRUD routes.
  2. Test the full navigation flow — The dashboard test (page.test.tsx:85-99) correctly asserted that the “New Project” link pointed to /dashboard/projects/new, but there was no corresponding test or page to handle that URL. Integration tests should cover the destination, not just the source.
  3. Buttons vs Links — Interactive elements that navigate should always be <Link> (or <a>), not <button>. Buttons without handlers are invisible failures — no error, no feedback, just nothing happens.
  4. Existing components were ready — The ProjectSetupWizard and useCreateProject hook were already built and waiting. The fix was purely a routing/wiring issue, not a missing feature.