Skip to content

interledger/interledger.org-v5

Repository files navigation

Interledger.org Website

Interledger Foundation

This repository contains the source code for the Interledger Foundation website, built with Astro, Starlight for documentation, and Strapi as a headless CMS.

It represents the fifth major iteration of interledger.org. For background on previous versions and the site’s evolution, see the project wiki.

Table of Contents

  1. About the Project

  2. Architecture Overview

  3. Project Structure

  4. Local Development

  5. CI / GitHub Workflows

  6. Content Workflow

  7. Contributing

  8. Summit Data (Sessionize Integration)

  9. More Info

About the Project

  • Astro provides a modern static site framework for fast, flexible site building.

  • Starlight adds a ready-made documentation system, including layouts, navigation, and styling, making it easy to write and maintain docs.

  • Strapi is the headless CMS for content management. Custom lifecycle hooks have been added to automatically synchronize content with the Astro project.

Styling

  • The frontend styling is built using Tailwind CSS.
  • Design tokens, utility conventions, and custom styles are documented separately: Styles README

Architecture overview

flowchart
    subgraph gcp["☁️ GCP VM"]
        direction TB
        appclone[("Repo Clone<br/>(running Strapi app)")]
        strapidb[("Strapi Database")]
        strapi[Strapi Admin portal]
        appclone -->|"runs from './cms' folder"| strapi
        strapi -->|"reads/writes"| strapidb
    end

    subgraph github["📦 GitHub Repository"]
        direction TB
        staging["staging branch"]
        main["main branch"]
    end

    subgraph netlify["🚀 Netlify"]
        direction TB
        preview["Preview Site"]
        stagingsite["Staging Site"]
        production["Production Site"]
    end

    editor["👤 Content Editor"]
    dev["👨‍💻 Developer"]
    feature["Feature Branch"]

    editor ==>|"Publish"| strapi
    dev ==>|"Code / Content PR"| feature

    strapi -->|"lifecycle hooks generate MDX & <br/> push via GitHub App"| staging

    appclone -.->|"sync:mdx script<br/>(after pulling staging)"| strapidb


    feature ==>|"PR (approved)"| staging
    feature ==>|"PR"| preview

    staging ==>|"Auto-build"| stagingsite
    staging -->|"PR"| main

    staging -.->|"Pulls updates when <br/> './cms' folder changes"| appclone

    main ==>|"Auto-build"| production

    classDef gcpStyle fill:#4285f4,stroke:#1967d2,color:#fff
    classDef portalStyle fill:#018501,stroke:#1967d2,color:#fff
    classDef githubStyle fill:#24292e,stroke:#000,color:#fff
    classDef netlifyStyle fill:#00c7b7,stroke:#008577,color:#fff
    classDef userStyle fill:#ff6b6b,stroke:#d63031,color:#fff
    classDef branchStyle fill:#6c5ce7,stroke:#5f3dc4,color:#fff

    class strapi portalStyle
    class appclone,stagingclone gcpStyle
    class staging,main,feature githubStyle
    class preview,production,stagingsite netlifyStyle
    class editor,dev userStyle
Loading

Project structure:

.
├── .github/
│   ├── workflows/
│   └── copilot-instructions.md
├── cms/        # Strapi backend
│   ├── config/              # Strapi configuration files
│   │   ├── admin.ts
│   │   ├── database.ts
│   │   ├── middlewares.ts
│   │   ├── plugins.ts
│   │   └── server.ts
│   ├── database/                      # Database files
│   │   └── migrations/
│   ├── scripts/              # e.g., sync:mdx, sync-navigation
│   ├── src/         # Astro frontend application
│   │   ├── admin/      # Admin UI customizations
│   │   ├── api/
│   │   │   ├──/{content-type}  # e.g., blog-post, foundation-page
│   │   │       ├── content-types/
│   │   │       │       ├── schema.json
│   │   │       │       └── lifecycles.ts  # MDX generation logic
│   │   │       ├── controllers/
│   │   │       ├── routes/
│   │   │       └── services/
│   │   │   └── utils.ts
│   │   ├── components/                # Reusable Strapi components
│   │   │   ├── blocks/                # Content block components
│   │   │   ├── navigation/
│   │   │   └── shared/                # Shared components
│   │   ├── serializers/               # MDX serialization logic
│   │   │   └── blocks/
│   │   ├── utils/
│   │   └── index.ts
│   └── types/                         # TypeScript type definitions
│   │   └── generated/
│   ├── .env                 # Environment variables
│   ├── .gitignore
│   ├── package.json
│   ├── pnpm-lock.yaml
│   ├── pnpm-workspace.yaml
│   ├── strapi-server.js
│   ├── tsconfig.json
│   ├── copy-schemas.js
│   └── README.md
├── public/           # Static assets (images, favicons, uploads)
│   └── uploads/      # User-uploaded media for Strapi local storage
├── src/              # Astro project
│   ├── components/    # Astro components
│   ├── config/        # JSON configs (navigation, etc.)
│   ├── content/       # Markdown/MDX content (blog, summit, docs)
│   │   ├── blog/
│   │   ├── developers/
│   │   ├── docs/
│   │   ├── foundation-pages/
│   │   └── summit/
│   ├── layouts/
│   ├── pages/       # Route pages
│   │   ├── blog/
│   │   ├── developers/
│   │   ├── summit/
│   │   ├── [...page].astro
│   │   └── index.astro
│   ├── schemas/
│   ├── styles/       # Global styles
│   ├── utils/        # Utility functions
│   ├── content.config.ts   # Astro content collections config
│   ├── env.d.ts
│   └── middleware.ts
├── .env                 # Environment variables
├── .env.example
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── astro.config.mjs
├── eslint.config.js
├── netlify.toml
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── tailwind.config.mjs
└── tsconfig.json

Local Development

Prerequisites

Environment Setup

  1. Clone the repository:
git clone https://github.com/interledger/interledger.org-v5.git
  1. Install dependencies:
pnpm install

Note on lockfiles: This repo has two pnpm-lock.yaml files:

  • /pnpm-lock.yaml — root workspace lockfile, used locally and in CI
  • /cms/pnpm-lock.yaml — standalone lockfile used by the GCP VM when deploying Strapi (cd cms && pnpm install)

When cms/package.json changes (e.g. upgrading Strapi), regenerate both:

pnpm install --no-frozen-lockfile          # from repo root
cd cms && pnpm install --no-frozen-lockfile # for GCP deployment
  1. Build and start the site:
# Build for production
pnpm run build

# Start dev server (localhost:1103)
pnpm run start
  1. For Strapi Admin setup locally, refer to the /cms/README.md.

🧞 Commands

All commands are run from the root of the project, from a terminal:

Command Action
pnpm install Installs dependencies
pnpm run start Starts local dev server at localhost:1103
pnpm run build Build your production site to ./dist/
pnpm run preview Preview your build locally, before deploying
pnpm run format Format code and fix linting issues
pnpm run lint Check code formatting and linting
pnpm run sync:sessionize -- <YEAR> Fetch Sessionize data (JSON + speaker images) for a given year

🔍 Code Formatting

This project uses ESLint for code linting and Prettier for code formatting. Before submitting a pull request, please ensure your code is properly formatted:

  1. Fix issues: Run pnpm run format to automatically format code and fix linting issues
  2. Check before pushing: Run pnpm run lint to verify everything passes (CI will also run this)

ESLint is configured to work with TypeScript and Astro files. The configuration extends recommended rules from ESLint, TypeScript ESLint, and Astro ESLint plugins, and integrates with Prettier to avoid conflicts.

CI / GitHub Workflows

GitHub Actions run automatically on pull requests and branch merges.

Workflows include:

  • Linting (ESLint)
  • Formatting validation (Prettier)
  • Build validation

Pull requests must pass all checks before merging.

Content Workflow

Content Synchronization

Astro is the source of truth for site content. Strapi lifecycles and synchronization scripts keep the CMS and Astro .mdx files in sync.

  1. Strapi → Astro:

    • Strapi lifecycle hooks trigger .mdx file creation, updates, and deletions.
    • Changes are automatically committed and pushed directly to the staging branch, where Strapi acts as a contributor.
  2. Astro → Strapi:

    • Merges in staging sync .mdx files back into the Strapi database.
    • Scripts like sync:mdx handle the synchronization.

Preview Functionality

  • Editors can preview content from Strapi in real time before publishing.
  • Page previews become available after saving content as a draft or after publishing.
  • While the rest of the site is statically generated, preview pages use server-side rendering in Astro (export const prerender = false in page-preview.astro).
  • Each content type is mapped to a corresponding preview route.

⚠️ Note: Netlify automatically generates Deploy Previews for pull requests opened against staging that can be accessed at: https://deploy-preview-{PR-number}--interledger-org-v5.netlify.app/. These previews are frontend-only and reflect the Astro build at that PR state.

For more information on Strapi lifecycles, synchronization scripts and preview functionality, see /cms/README.md.

Branches and Deployment

  • main:

    • Serves the live production website.
    • Merges to main trigger a Netlify rebuild of the production site.
  • staging:

    • Serves the live staging website (deployed via Netlify).
    • Serves the Strapi Admin interface (running on the GCP VM).
    • Any push to staging that modifies files in /cms triggers a rebuild of the Strapi Admin panel on the GCP VM.
    • Any push to staging that modifies .md or .mdx files in src/content/foundation-pages, src/content/summit-pages, src/content/foundation-blog-posts, or src/content/ambassadors also triggers sync:mdx, including their localized mirrors under src/content/<locale>/....

Hosting Architecture

  • The Astro website (production and staging) is deployed and hosted via Netlify.
  • Strapi (including the Admin panel) runs on a single Google Cloud VM.
  • The Strapi instance on the VM tracks the staging branch and pulls updates when /cms changes are merged.

Environments

Contributing

Content can be added in two main ways:

  • Editor workflow (via Strapi Admin)
  • Developer workflow (via Astro .mdx files)

The developer workflow also includes adding and maintaining documentation.

There are three contribution paths, depending on your role and the type of content.

1. Editor flow (Strapi workflow)

  • Editors create pages and blog posts via Strapi Admin.
  • Each content type in Strapi has lifecycles configured to generate/update/delete .mdx files in the Astro project automatically.
    • Example: Creating a foundation page writes MDX under src/content/foundation-pages/ using nested folders from the full path slug (see below): English uses the last segment as the filename; localized pages are written under the collection-level /{locale}/ directory with the nested slug folders beneath it.
  • Content changes are automatically committed and pushed to the staging branch by the GitHub App Interledger Strapi.

⚠️ Note: Strapi is set up to be a contributor to our code base. When editors use the Strapi interface to make changes, Strapi's lifecycle hooks make commits to the staging branch on behalf of the editors.

Content Management Documentation

All documentation for working with website content is available in the wiki. Please refer to the wiki for:

  • Content creation and editing guidelines
  • Adding blog posts and podcast episodes
  • Managing multilingual content
  • General site-building philosophy

2. Developer flow - Website Content (Astro Content Collections)

Developers can add multiple types of content directly to the repository. Each content type has a specific folder and naming convention.

Astro automatically picks up these files, registers them in the appropriate content collection, and generates the correct routes using the associated templates.

Content paths vs URL routes

This project has two related but separate pieces of configuration:

  • Filesystem content paths: where MDX files live under src/content/...
  • URL route bases: where those collections are exposed under src/pages/...

These should not be treated as interchangeable.

Examples:

  • src/content/foundation-pages maps to site routes at /...
  • src/content/foundation-blog-posts maps to /blog/...
  • src/content/developers-blog-posts maps to /developers/blog/...
  • src/content/summit-pages maps to /summit/...

The main source files for this setup are:

  • src/content.config.ts Defines Astro collection ids such as 'foundation-pages', 'foundation-blog', 'developers-blog', and 'summit-pages'.
  • src/utils/paths.ts Defines filesystem paths and folder names used to load content from disk.
  • src/utils/routes.ts Defines ROUTE_BASES, the URL base path for each content collection. Use this when building links, language-switcher URLs, or other route-aware behavior.
  • src/utils/static-paths.ts Builds localized static paths for collection-backed routes. EN is canonical; ES routes may render EN content when no ES translation exists.
  • src/utils/i18.ts Centralizes locale definitions and language-switcher ordering.

Rule of thumb:

  • If you are working with folders or files on disk, use src/utils/paths.ts
  • If you are working with browser URLs or route generation, use src/utils/routes.ts

When adding a new localized collection or changing route structure, review all of the files above together. They form the core configuration for how content is loaded and how URLs are generated.

Foundation Blog posts

  • Location: src/content/foundation-blog-posts
  • Localizations: src/content/foundation-blog-posts/{locale}
  • Filename format: YYYY-MM-DD-slug.mdx

Used for: Foundation news, updates, announcements, thought leadership.

Tech Blog posts

  • Location: src/content/developers-blog-posts
  • Localizations: src/content/developers-blog-posts/{locale}
  • Filename format: YYYY-MM-DD-slug.mdx

Used for: Technical deep dives, implementation updates, engineering insights.

Foundation Pages

  • Location: src/content/foundation-pages
  • Localizations: src/content/foundation-pages/{locale}/{parent...}/ (see path slug rules below)
  • Filename: last segment of the full path slug + .mdx (nested segments become parent directories)

Used for: Static foundation pages such as About, Policy & Advocacy, Team, Grants, etc.

Summit Pages

  • Location: src/content/summit-pages
  • Localizations: same nesting pattern as foundation pages
  • Filename: last segment of the full path slug + .mdx

Used for: Summit landing pages, schedules, speaker lists, event resources.

Foundation & Summit routes: Full Path Slug (pathSlug)

In Strapi this is a single field (“Full Path Slug”): the full URL path of the page, without a leading slash. The same value is stored in MDX frontmatter as pathSlug. The live site URL is /{pathSlug} (normalized, no duplicate slashes).

Examples:

pathSlug (frontmatter / Strapi) Public URL
about-us /about-us
grant/grant-for-web /grant/grant-for-web

On disk (English): split pathSlug on /; all segments except the last are folders; the last segment is the filename.

  • about-usfoundation-pages/about-us.mdx
  • grant/grant-for-webfoundation-pages/grant/grant-for-web.mdx

Localized pages live under one collection-level locale folder, with nested path segments after it (e.g. foundation-pages/es/grant/…mdx for Spanish).

Example (nested grant page):

---
pathSlug: 'grant/grant-for-web'
---

→ public URL: /grant/grant-for-web

Key rules:

  • pathSlug is required on all foundation and summit pages (the build will fail without it).
  • Leading and trailing slashes on pathSlug are stripped when parsing content.
  • There is no separate path field in Strapi or frontmatter for these types; use one multi-segment pathSlug for nested URLs.
  • If pathSlug is omitted from frontmatter (not allowed for a valid build), sync tooling may derive a default from the filename (without extension and without any YYYY-MM-DD- date prefix); nested URLs should use explicit folders + filename that match the intended pathSlug, or set pathSlug in frontmatter.

⚠️ Important (Schema Validation)

  • Use correct frontmatter for each content type.
  • Follow the required schema — invalid metadata will break the build.
  • See:
    • src/schemas/content.ts
    • src/content.config.ts
      to understand the required schema and validation rules for each content collection.

Blog metadata and tags

Each blog post includes frontmatter at the top of the file (title, description, date, authors, etc.), including a tags field used for filtering on the blog index.

Please only use the existing, approved tags unless you have aligned with the tech + comms team on adding a new one. This helps keep the tag filter focused and avoids fragmentation.

Current tags:

  • Interledger Protocol
  • Open Payments
  • Web Monetization
  • Rafiki
  • Updates
  • Releases
  • Card Payments

If you believe your post needs a new tag, propose it in your PR description or in the #tech-team Slack channel so we can decide whether to add it and update this list.

3. Developer flow - Documentation (Starlight)

Documentation pages are managed via Starlight.

Docs live in src/content/docs.

Starlight looks for .md or .mdx files in the src/content/docs/ directory. Each file is exposed as a route based on its file name.

Static assets, like favicons or images, can be placed in the public/ directory. When referencing these assets in your markdown, you do not have to include public/ in the file path, so an image would have a path like:

![A lovely description of your beautiful image](/img/YOUR_BEAUTIFUL_IMAGE.png)

For more information about the way our documentation projects are set up, please refer to our documentation style guide.

Developer Contribution Requirements

  • Add .mdx content in Astro.
  • Open PRs against staging.
  • Use frontmatter correctly — invalid metadata will break the build.
  • Run pnpm run build and pnpm run format before PR.
  • The PR must undergo review and pass all checks before it can be merged.

Consult Writing Guidelines for Developers below for more details on content structure, metadata, tags, and blog formatting.

Writing guidelines for developers

Goal: Educate, drive adoption, and grow strategic influence.

Typical Target Audience:

  • Technically-inclined users interested in Interledger development.
  • Technically-inclined users interested in financial services technologies, innovations, or developments.
  • Users keen on topics like APIs, data analytics, metrics, analysis, and quantitative assessment for digital networks.
  • Users interested in privacy and related technologies.

Possible Content Framework:

If you're unsure how to structure your writing, you can use this as a guide.

  • Introduction / main point
  • Context - Interledger’s perspective / stance / commitment on the topic being written [broader categories like privacy, metrics for growth, Digital Financial Inclusion etc.]
  • The Challenge (or) The Problem
  • The Solution
  • The How / implementation
  • Roadmap - short-term / long-term
  • Note: A call to action (CTA) will be included automatically at the bottom of every post.

Ideal Word Count: Between 1,000 and 2,500 words, with links to relevant documents/pages for a deeper understanding.

Getting Started

Discuss Ideas: Before starting, share your blog post ideas with the tech team to ensure alignment and awareness.

Copy the Template: Begin your draft using this Google Doc template to maintain a consistent format.

Review Process

Initial Reviews:

  • Once your draft is ready, request specific reviewers or ask for feedback on the #tech-team Slack channel.
  • Incorporate feedback and refine the blog post.

Finalizing:

  • When the draft is stable, create a pull request in the interledger.org GitHub repo against staging.
  • Please add links where appropriate so people can easily click to learn more about the concepts you reference.
  • Include all images used in the post in the PR.
  • No-one is expected to know the ins and outs of Astro (the framework that powers our site), so please tag someone in the frontend team as a reviewer to ensure everything Astro-related is in order.
  • The PR will be reviewed by the frontend team before being merged into staging.

Working with Visuals

  • If you need an illustration, submit a design request in advance to Madalina via the #design Slack channel using the design request form.
  • Before uploading images to GitHub, run them through an image optimizer such as TinyPNG.
  • Ensure images are appropriately sized; feel free to ask Madalina or Sarah for assistance.

Publishing Your Blog Post

  • Note: Merging the pull request will not publish the blog post immediately. Changes from staging are merged into main twice a week.
  • Ensure the publishing date in the blog post frontmatter matches the intended release date.
  • Check with Ioana to confirm the publishing date and keep a consistent posting schedule. Ioana will also handle social media promotion.
  • Run pnpm run build locally to verify that the page builds correctly.
  • Run pnpm run format and pnpm run lint to format your code and check for any issues before creating a pull request.

Summit Data (Sessionize Integration)

Overview

The Interledger Summit has taken place annually since 2022. Each edition has its own pages on the website — sessions(talks), speakers, and their individual detail pages — all scoped by year (e.g. /summit/2024/speakers, /summit/2024/talks).

All summit data originates from Sessionize. A sync script fetches that data and stores it locally in the project as JSON files. Utility functions then read those files to populate Astro components and generate all summit-related pages for every year automatically.

Syncing Data from Sessionize

Run the following script to fetch summit data for a given year:

pnpm run sync:sessionize -- <YEAR>

Example:

pnpm run sync:sessionize -- 2022
pnpm run sync:sessionize  # defaults to currentSummitYear

What is does:

  • Defaults to currentSummitYear if no year is provided
  • Downloads speaker and talk data into:
    • src/data/sessionize/{YEAR}-speakers.json
    • src/data/sessionize/{YEAR}-talks.json
  • Downloads speaker images into:
    • public/sessionize-speakers/img/{YEAR}
  • Clears the image folder before downloading
  • Validates the year against the allowed YEARS list

How the Data Is Used

Once the JSON files are in place, two utility files handle all data access and page generation — no manual wiring is needed.

extractSessionize.ts

Responsible for:

  • Reading local Sessionize JSON files
  • Normalizing data into internal types (Talk, Speaker, etc.)
  • Linking talks and speakers
  • Handling translations
  • Generating local image paths for speakers and adding a fallback image when missing

summit-talks-speakers.ts

Responsible for connecting processed data to Astro routing.

It generates:

  • Paginated listing pages
    • Talks → /summit/{year}/talks
    • Speakers → /summit/{year}/speakers
  • Dynamic detail pages
    • Talk pages → /summit/{year}/talk/{talk-title}
    • Speaker pages → /summit/{year}/speaker/{speaker-name}

All of these functions iterate over every year in the YEARS list automatically, so new summit data is picked up without any changes to page templates.

Adding a New Summit Year

  1. Add a new entry to sessionizeApiMap in src/utils/sessionize.ts, using the summit year as the key (e.g. '2026') and the corresponding Sessionize API URLs as values:
  '2026': {
    speakersUrl: 'https://sessionize.com/api/v2/.../view/Speakers',
    talksUrl: 'https://sessionize.com/api/v2/.../view/Sessions'
  }
  • YEARS and currentSummitYear will update automatically — no other changes needed.
  1. Run the script pnpm run sync:sessionize to fetch data and images for the new summit.

After syncing, it is recommended to check:

  • That the fields in the new JSON files match those from previous years. In practice, they have always matched, but it is a good habit to verify.
  • The hardcoded IDs used for translations (see Translations below), in case Sessionize has changed them.

Translations

From 2025 onwards, summit content includes Spanish translations. These are stored by Sessionize inside a questionAnswers array, present on both speaker and talk objects, using a structure like:

{
  "id": 114105,
  "answer": "Título en español"
}

The following IDs are hardcoded in src/utils/extractSessionize.ts:

Constant ID Used for
SPANISH_TITLE_ID 114105 Spanish title of a talk
SPANISH_DESC_ID 114099 Spanish description of a talk
SPANISH_BIO_ID 114100 Spanish bio of a speaker
TRANSLATION_ID 107734 Available translation languages for a talk

When importing data for a new summit, verify that these IDs have not changed in the Sessionize export. If they have, update the constants in extractSessionize.ts accordingly.

Adding Support for a New Language

To add a new language to the Sessionize data pipeline:

  1. Add the locale code to SESSIONIZE_SUPPORTED_LOCALES in src/types/summit.ts:
export const SESSIONIZE_SUPPORTED_LOCALES = ['es', 'fr'] as const
  1. Update the utility functions in src/utils/extractSessionize.ts to extract the new language's fields from questionAnswers, following the same pattern used for Spanish. Each function should add a new key to the returned translations object (e.g. fr: { title, description }) alongside the existing es: {} entry. You will also need to add the corresponding Sessionize question IDs as constants (same as SPANISH_TITLE_ID, SPANISH_DESC_ID, etc.).

Image Handling

  • Speaker images are downloaded locally during sync
  • Stored under: public/sessionize-speakers/img/{YEAR}/
  • Filenames are generated using a slugified speaker name
  • If no image is available, a fallback is used: public/sessionize-speakers/img/no-photo.svg

More Info

Details on Strapi lifecycles, MDX syncing, and preview functionality, and how to set up a local Strapi instance are documented in /cms/README.md.

About

interledger.org built on astro with strapi for cms editing

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors