Templates

PortalJS ships two starting points. Both are real Next.js projects with the same lightweight components/Table.tsx, Tailwind setup, and Next 14 config — they differ only in how dataset pages are routed. /new-portal picks the right one for your brief; you can also clone either by hand.

The two templates

portaljs-template (default)portaljs-catalog
Dataset pagesone .tsx file per datasetone dynamic [slug].tsx for all
Registrationhardcoded array in index.tsxdatasets.json manifest
Adding a datasetnew page file + array entryone JSON entry + a data file
Best fora handful of datasetsdozens to hundreds

Default template — one page per dataset

examples/portaljs-template is the canonical template. Each dataset is its own pages/datasets/<slug>.tsx file, registered in a datasets array on the home page. This is the simplest mental model — one file, one page — and it has no getStaticPaths, so it static-exports without fuss. /add-dataset writes one file per dataset against this template.

Clone it by hand with degit:

npx degit datopian/portaljs/examples/portaljs-template my-portal

Catalog variant — one dynamic route

examples/portaljs-catalog is for portals with many datasets. Instead of a file per dataset, the catalog is driven by a single datasets.json manifest and rendered by a dynamic pages/datasets/[slug].tsx route with getStaticPaths. Adding a dataset is one JSON entry plus a data file — no new page:

{ "slug": "my-data", "name": "My Data", "description": "…", "file": "my-data.csv", "format": "csv" }
npx degit datopian/portaljs/examples/portaljs-catalog my-catalog

Which should I use?

  • A handful of datasets, or you want each page to be its own editable file → the default template.
  • Dozens to hundreds of datasets, driven from a manifest → the catalog variant.
  • A live backend (CKAN, etc.) → start from either, then run /connect-ckan — it replaces the dataset route with one that fetches from the backend.

Both static-export cleanly (the catalog pre-renders every manifest entry via getStaticPaths with fallback: false).

Where to go next