A Solid-based volunteer profile manager built with Next.js. Volunteers authenticate with their Solid identity, and all their volunteering data — skills, causes, equipment, availability, locations, and credentials — is stored in their own Solid Pod, giving them full ownership and control.
Built by The ODI as a demonstrator for the Volunteering Data standard.
| Feature | Description |
|---|---|
| Profile | Reads the user's WebID profile (name, email, photo, social links, etc.) and displays it on the home page. |
| Skills & Causes | Select skills and causes of interest from the volunteer ontology. Stored as RDF in the Pod. |
| Equipment | Declare equipment/resources you can provide — ontology items or custom entries. |
| Preferred Locations | Pick locations on an interactive map with configurable radius. Stored as structured RDF blank nodes. |
| Availability | Weekly scheduler grid (morning / afternoon / evening × 7 days). Persisted as composed time IRIs. |
| Credentials | View, collect, and verify credentials (e.g. UK DBS Check). Stored in the Pod alongside profile data. Mock issuer verification page included. |
| Solid Auth | Full OIDC login flow via @ldo/solid-react and solid-react-component. All Pod requests use an authenticated fetch with DPoP headers. |
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript 5 |
| Styling | Tailwind CSS 4 |
| Solid Auth | @ldo/solid-react + solid-react-component |
| RDF Parsing | n3 (Parser, Store, Writer, DataFactory) |
| RDF Wrapper | @rdfjs/wrapper (TermWrapper, DatasetWrapper) |
| Data Caching | @tanstack/react-query |
| Maps | Leaflet + React Leaflet |
| Icons | Heroicons |
- Node.js ≥ 20
- pnpm (recommended) — or npm / yarn
- A Solid Pod and identity provider (e.g. Community Solid Server, solidcommunity.net, or any Solid-OIDC provider)
pnpm installpnpm devOpen http://localhost:3000. You'll be redirected to the login page to authenticate with your Solid identity provider.
pnpm build
pnpm startpnpm lint├── app/
│ ├── layout.tsx # Root layout — providers, auth guard, ontology context
│ ├── providers.tsx # BrowserSolidLdoProvider + React Query
│ ├── page.tsx # Home — profile page
│ ├── login/page.tsx # Solid OIDC login page
│ ├── volunteer-info/page.tsx # Skills, causes, equipment, locations
│ ├── availability/page.tsx # Weekly availability scheduler
│ ├── credentials/
│ │ ├── page.tsx # Credential list (Pod-backed)
│ │ └── verify/[id]/page.tsx # Mock issuer verification / claim / unclaim
│ ├── components/ # UI components (profile, credentials, map, nav, etc.)
│ ├── contexts/ # React contexts (sidebar, volunteer ontology)
│ └── lib/
│ ├── class/ # @rdfjs/wrapper classes (Agent, VolunteerProfile, Vocabulary)
│ ├── helpers/ # Pure-async Pod read/write helpers
│ └── hooks/ # React hooks (useAgent, usePodRoot, useCredentials, etc.)
├── ontology/
│ └── volunteer.ttl # Volunteer ontology (SKOS concept schemes)
├── docs/
│ └── skills/solid/ # Developer docs (RDF wrapper, Solid React, data modelling, etc.)
├── public/ # Static assets (fonts, ontology, icons)
├── next.config.ts # Next.js config (transpile solid-react-component, LDO aliases)
├── package.json
└── tsconfig.json
All volunteer-specific data is stored in a single Turtle document in the user's Pod:
{podRoot}/volunteer/profile.ttl
Subject: <{podRoot}/volunteer/profile.ttl#me>
| Data | RDF predicate | Shape |
|---|---|---|
| Skills | vp:hasSkill |
Named node IRIs (ontology concepts) |
| Causes | vp:preferredCause |
Named node IRIs |
| Equipment | vp:hasRequirement |
Named node IRIs (ontology or custom URNs) |
| Preferred times | vp:preferredTime |
Composed IRIs (e.g. volunteering:MondayMorning) |
| Locations | vp:preferredLocation |
Blank nodes → vp:Point (geo:lat, geo:long) + vp:rad + rdfs:label |
| Credentials | vp:hasCredential |
Blank nodes → vp:Credential with type, issuer, status, title, issuedAt |
The user's WebID profile (name, email, photo, storage URLs) is read from their existing profile document — it is not duplicated.
| Prefix | IRI |
|---|---|
vp: |
https://id.volunteeringdata.io/volunteer-profile/ |
volunteering: |
https://ns.volunteeringdata.io/ |
geo: |
http://www.w3.org/2003/01/geo/wgs84_pos# |
rdfs: |
http://www.w3.org/2000/01/rdf-schema# |
Every Pod request uses the authenticated fetch from @ldo/solid-react:
const { fetch: authFetch } = useSolidAuth();The Pod storage URL is derived from the WebID profile (pim:storage / solid:storage):
const { podRoot, webId } = usePodRoot();- GET the Turtle document → parse with N3
Parser→Store - Wrap with
VolunteerProfile(@rdfjs/wrapperTermWrapper) → read via getters - Mutate the live
Set(simple IRIs) or build blank nodes withDataFactory(structured data) - Serialize with N3
Writer→ PUT back
All data is cached per-user with staleTime: Infinity. Saves optimistically update the cache, then write to the Pod (debounced for rapid edits, immediate for intentional actions like credentials).
The volunteer ontology (ontology/volunteer.ttl) defines SKOS concept schemes for:
- Skills — e.g. First Aid, Driving, IT Support
- Causes — e.g. Environment, Education, Health
- Equipment / Requirements — e.g. DBS Check, First Aid Kit, Van
It is parsed at build time on the server and provided to the app via React context.