JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 31
  • Score
    100M100P100Q73504F
  • License MIT

Self-contained project portfolio components for Next.js App Router. Includes ProjectPortfolio, ProjectDetail, ProjectMenu (megamenu), and GalleryCarousel. Pass a clientSlug and apiBase — done.

Package Exports

  • project-portfolio

Readme

project-portfolio

A suite of self-contained project portfolio components for Next.js App Router. Drop in a clientSlug and apiBase — each component fetches, caches, and renders everything on the server with zero client-side waterfall requests.

Requirements

  • Next.js 13+ (App Router)
  • React 18+

No other dependencies required.

Installation

npm install project-portfolio

Components

ProjectPortfolio

A full project grid page. Fetches all projects for a client and renders them as responsive cards (1 column on mobile, 2 on tablet, 3 on desktop).

// app/projects/page.tsx
import { ProjectPortfolio } from "project-portfolio"

// Must be a Server Component — do NOT add "use client"
export default async function ProjectsPage() {
  return (
    <ProjectPortfolio
      clientSlug="your-client-slug"
      apiBase="https://your-api.com"
      basePath="/projects"
    />
  )
}
Prop Type Required Default Description
clientSlug string Yes Identifies which client's projects to load
apiBase string Yes Base URL of the projects API
basePath string No "/projects" Base path for project detail links
searchParams Record<string, string | string[] | undefined> No {} Filter params forwarded to the API — pass Next.js searchParams directly
revalidate number No 60 Cache revalidation period in seconds

Filter Integration

ProjectPortfolio works together with ProjectMenu to form a complete filter flow. When a user clicks a filter link in the megamenu, they are navigated to the project grid with filter query params appended to the URL. Pass Next.js searchParams to ProjectPortfolio so it can forward them to the API:

// app/projects/page.tsx
import { ProjectPortfolio } from "project-portfolio"

export default async function ProjectsPage({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined }
}) {
  return (
    <ProjectPortfolio
      clientSlug="your-client-slug"
      apiBase="https://your-api.com"
      basePath="/projects"
      searchParams={searchParams}
    />
  )
}

Filter URLs follow this pattern — the key matches the custom field key in the schema:

/projects?type=commercial
/projects?type=educational-facilities
/projects?system=spacematic

When active filters are applied, a filter banner is shown above the grid with a "Clear filters" link back to basePath.


ProjectDetail

A full project detail page. Fetches a single project by slug and renders its gallery, custom fields, related projects, and a back link.

// app/projects/[slug]/page.tsx
import { ProjectDetail } from "project-portfolio"

// Must be a Server Component — do NOT add "use client"
export default async function ProjectPage({ params }: { params: { slug: string } }) {
  return (
    <ProjectDetail
      slug={params.slug}
      clientSlug="your-client-slug"
      apiBase="https://your-api.com"
      backPath="/projects"
      backLabel="All Projects"
    />
  )
}
Prop Type Required Default Description
slug string Yes The project slug to load
clientSlug string Yes The client slug that owns this project
apiBase string Yes Base URL of the projects API
backPath string No "/projects" Path for the back navigation link
backLabel string No "All Projects" Label for the back navigation link
revalidate number No 60 Cache revalidation period in seconds

ProjectMenu

A compact megamenu component. Shows featured projects as list-style cards on the left and "Browse By" filter links on the right. Designed to be dropped directly into a navigation megamenu dropdown.

// components/navigation/MegaMenu.tsx
import { ProjectMenu } from "project-portfolio"

// Must be a Server Component — do NOT add "use client"
export async function ProjectsMegaMenu() {
  return (
    <ProjectMenu
      clientSlug="your-client-slug"
      apiBase="https://your-api.com"
      basePath="/projects"
      subtitle="Our systems are installed in every geographic region of the U.S. and in a variety of applications."
      maxProjects={6}
    />
  )
}
Prop Type Required Default Description
clientSlug string Yes Identifies which client's projects to load
apiBase string Yes Base URL of the projects API
basePath string No "/projects" Base path for project detail links
viewAllPath string No Same as basePath Path for the "View All Projects" link
subtitle string No Description paragraph shown above the project cards
font string No System font stack Font family string applied to all inline styles
maxProjects number No 6 Maximum number of projects to display
revalidate number No 60 Cache revalidation period in seconds

The filter options in the right sidebar are driven automatically by the is_filterable fields returned from the API schema — no manual configuration needed.


Important: Server Component Usage

All three components (ProjectPortfolio, ProjectDetail, ProjectMenu) are async Server Components. They must be rendered in a server context:

// CORRECT
export default async function Page() {
  return <ProjectPortfolio clientSlug="..." apiBase="..." />
}

// WRONG — causes infinite client-side fetch loop
"use client"
export default function Page() {
  return <ProjectPortfolio clientSlug="..." apiBase="..." />
}

If a parent component uses "use client", these components cannot be rendered inside it directly. Pass them as children from a server component instead.


Caching

Data is cached at two levels:

  1. Per-renderReact.cache() deduplicates any duplicate calls within the same render pass
  2. Cross-requestnext: { revalidate: N } caches responses in Next.js's built-in Data Cache

No third-party caching libraries are required.


Publishing to npm

# Log in to npm
npm login

# Build and publish
cd package
npm run build
npm publish --access public

To release an update, bump the version field in package/package.json then run npm publish again.