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-portfolioComponents
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=spacematicWhen 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:
- Per-render —
React.cache()deduplicates any duplicate calls within the same render pass - Cross-request —
next: { 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 publicTo release an update, bump the version field in package/package.json then run npm publish again.