DARIAH-Campus is a combined discovery layer and hosting platform for DARIAH learning resources. This guide will help you understand the project architecture and how to contribute.
- Next.js 15: The main framework for building the application
- TypeScript 5: For type-safety
- Tailwind CSS 3: For styles with utility classes
- Node.js 22: Required runtime environment
- pnpm 10: Package manager
- Used for content management
- Provides a user-friendly interface for content editing
- Content is stored in MDX format
- Authentication is handled via GitHub OAuth, which is configured via this GitHub App
- Sign-in is available at
/admin
- Configuration can be found in
keystatic.config.tsx
- When developing locally, and no configuration for GitHub OAuth has been provided via environment variables, the CMS will save changes to the local filesystem and not commit to GitHub
- Official docs: https://keystatic.com/docs
- Powers the search functionality
- Schema is defined in
lib/typesense/schema.ts
- Search index is automatically built during deployment via
scripts/typesense/create-search-index.ts, which is
triggered by the
build:search-index
npm script - Uses off-the-shelf InstantSearch components for search UI
- Docker compose config available for local development
- Official docs: https://typesense.org/docs/
.
├── app/ # Next.js app router components
│ ├── (app)/ # Main application routes
│ └── (cms)/ # Keystatic CMS routes
├── config/ # Configuration
├── components/ # Shared components
├── content/ # Content files in MDX format
│ ├── en/ # English content
├── lib/ # Shared utilities
│ ├── content/ # Content-related utilities
│ ├── i18n/ # Internationalization
│ ├── keystatic/ # Keystatic configuration and helpers
│ └── typesense/ # Typesense configuration and helpers
├── public/ # Static assets
└── scripts/ # Build and utility scripts
Components and utilities which are needed in more than one route should be saved in the top-level
components
and lib
folders. Components and utilities which are only needed in a single route
should instead be co-located with that route in a _components
or _lib
folder. For example:
.
└── app/
└── (app)/
└── search/
├── _components/ # Components for search UI
└── _lib/ # Utilities for search
The project follows Next.js app router conventions and uses kebab-case for file and folder naming These conventions are enforced with a lint rule:
"check-file/filename-naming-convention": [
"error",
{
"**/*": "KEBAB_CASE",
},
{ ignoreMiddleExtensions: true },
],
"check-file/folder-naming-convention": [
"error",
{
"**/": "NEXT_JS_APP_ROUTER_CASE",
},
],
All environment variables should be listed in .env.local.example
, and
validated at runtime with the schema defined in config/env.config.ts
.
NEXT_PUBLIC_APP_BASE_URL
: base url of deployment domainNEXT_PUBLIC_REDMINE_ID
: Redmine id needed for the acdh-ch imprint serviceNEXT_PUBLIC_BOTS
: whether web crawler are allowed to index the site. allowed values are "disabled" and "enabled". defaults to "disabled", but is set to "enabled" for production deployments on VercelENV_VALIDATION
: whether environment variable validation is enabled. allowed values are "disabled", "public" (which will only validate build-args prefixed withNEXT_PUBLIC_
) and "enabled". defaults to "enabled"NEXT_PUBLIC_MATOMO_BASE_URL
andNEXT_PUBLIC_MATOMO_ID
: configuration for Matomo analytics serviceNEXT_PUBLIC_GOOGLE_SITE_VERIFICATION
: Google Search Console property verification token
NEXT_PUBLIC_TYPESENSE_COLLECTION
: Typesense collection nameNEXT_PUBLIC_TYPESENSE_HOST
: Typesense server hostNEXT_PUBLIC_TYPESENSE_PORT
: Typesense server portNEXT_PUBLIC_TYPESENSE_PROTOCOL
: Typesense server protocolNEXT_PUBLIC_TYPESENSE_SEARCH_API_KEY
: Typesense search-only api key, scoped to specific collectionTYPESENSE_ADMIN_API_KEY
: Typesense admin secret
KEYSTATIC_GITHUB_CLIENT_ID
: GitHub client id, necessary for GitHub OAuthKEYSTATIC_GITHUB_CLIENT_SECRET
: GitHub client secret, necessary for GitHub OAuthKEYSTATIC_SECRET
: Keystatic secretNEXT_PUBLIC_KEYSTATIC_GITHUB_APP_SLUG
: Identifier of the GitHub App used for authenticationNEXT_PUBLIC_KEYSTATIC_GITHUB_REPO_NAME
: GitHub repositoryNEXT_PUBLIC_KEYSTATIC_GITHUB_REPO_OWNER
: GitHub organisationNEXT_PUBLIC_KEYSTATIC_MODE
: whether to store changes to the local filesystem, or commit changes to GitHub. defaults to "local", should be set to "github" in production
Content is managed using Keystatic CMS. The content structure includes:
- Resources (events, external, hosted, pathfinders)
- Curricula
- Tags (Topics)
- People
- Sources
- Documentation pages
Each content type is defined with a Keystatic collection schema in lib/keystatic/collections.ts.
Custom components, which can be used as rich-text editor widgets, are defined in lib/keystatic/collections.ts. To correctly render these custom components in the app, a React component must also be provided to the component mapping in mdx-components.tsx.
MDX content is transformed to React components with the processing pipeline defined in config/mdx.config.ts, which adds plugins to:
- add support for GitHub flavored markdown (strikethrough, tables, etc.)
- add support for footnotes
- transform regular quotes and dashes into their typographic variants
- add
id
attributes to headings, so they can be targeted by url-fragment links - extracts a table of contents
- adds support for syntax-highlighting in code blocks
In production, GitHub
branch protection rules enforce,
that nobody (except repository administrators) are allowed to push changes directly to the main
branch, but are required to create a new branch and open a pull request. The Keystatic UI helps
doing that.
The search functionality is powered by Typesense. The search schema is defined in lib/typesense/schema.ts.
- Start a local Typesense server
Create an api key and provide it as the TYPESENSE_ADMIN_API_KEY
environment variable in
.env.local
. Then run:
pnpm run dev:typesense:up
- Create a search-only api key:
pnpm run typesense:api-key:create
This will print the api key to the console, which you then need to set as
NEXT_PUBLIC_TYPESENSE_SEARCH_API_KEY
in .env.local
:
- Create and seed the collection:
pnpm run typesense:collection:create
pnpm run typesense:collection:seed
Metadata for the "resources" ("events", "external", "hosted", "pathfinders") and "curricula" collections is also available via API.
There are legacy API endpoints available at
/api/metadata/:(curricula|events|resources)
, which simply serves a
dump of legacy content (in the previous metadata format). New content is currently not added.
New API endpoints serving fresh metadata (in the updated metadata schema) are available at /api/v2/metadata/curricula and /api/v2/metadata/resources.
- Install dependencies:
pnpm install
- Copy the .env file template and set required environment variables:
cp .env.local.example .env.local
- Start development server
pnpm run dev
- Formatting, linting, and type-checking
pnpm run format:check
pnpm run lint:check
pnpm run types:check
Auto-fixable formatting and lint errors can be fixed with:
pnpm run format:fix
pnpm run lint:fix
The production app is hosted on Vercel. Every push to GitHub triggers a fresh deployment. Preview deployments from pull-request branches can be viewed publicly by pressing the "View deployment" button in the pull request comments.
The Vercel Dashboard, where environment variables and secrets can be set, is available at https://vercel.com/dariah/dariah-campus (invitation required).