The official website for Measurement Lab, an open-source project providing Internet performance measurement tools and data.
npm install
npm run dev # Development server at localhost:4321
npm run build # Production build
npm run preview # Preview production build- Astro 5.16 - Static site generation
- Tailwind CSS 4 - Utility-first styling
- React 19 - Interactive components
- Pages CMS - Git-based content management
src/
├── assets/ # Images, logos, icons
├── components/
│ ├── atoms/ # Basic elements (Button, Link, Tag, Text, Heading)
│ ├── molecules/ # Compound components (Card, FormField, NavItem)
│ ├── organisms/ # Complex components (Navigation, Footer, Hero, Search)
│ ├── sections/ # Page section templates
│ └── landing/ # Custom landing page components (blog, publications)
├── content/ # All CMS-managed content (see below)
├── layouts/ # Page layout templates
├── lib/ # Shared libraries and utilities
├── pages/ # Route definitions
├── styles/ # Tailwind CSS configuration
└── utils/ # Helper functions
All content lives in src/content/ with type-safe schemas defined in src/content/config.ts.
| Collection | Format | Purpose |
|---|---|---|
pages/ |
YAML | Static pages (about, contact, policies, etc.) |
blog/ |
Markdown | Blog posts with frontmatter |
publications/ |
JSON | Research publications, papers, presentations |
people/ |
JSON | Team member profiles |
partners/ |
JSON | Partner organizations |
tests/ |
Markdown | M-Lab test documentation |
navigation/ |
JSON | Menu structure (main.json, footer-1.json, footer-2.json) |
site/ |
JSON | Global site configuration (config.json, _redirects.json) |
homepage/ |
YAML | Homepage-specific content |
categories/ |
JSON | Category definitions for blog, people, partners, publications |
Pages use a flexible section-based system. Each page YAML file contains a sections array where you compose the page from available section types.
| Type | Component | Purpose |
|---|---|---|
hero |
HeroSection | Page title with optional zigzag background |
richText |
RichTextSection | Markdown content with optional table of contents |
button |
ButtonSection | Call-to-action buttons |
card |
CardSection | Card grid layouts |
people |
PeopleSection | Team member listings (filtered by category) |
partners |
PartnersSection | Partner organization display |
blog_roll |
BlogRollSection | Latest blog posts (configurable limit) |
speed_test |
SpeedTestSection | M-Lab speed test widget |
featured_partners |
FeaturedPartnersSection | Partner spotlight |
flexi |
FlexiSection | Nested section container |
All sections support a background property:
background:
color: white # Options: white, gray, primary-light, primary-medium, primary-dark
image: /src/assets/my-image.jpg # Optional background imageHero sections support zigzag decorative backgrounds:
zigzag:
primary-light # Options: primary-light, primary-dark, secondary-light,
# secondary-dark, supporting1-light, supporting1-dark,
# supporting2-light, supporting2-darkNavigation is defined in JSON files in src/content/navigation/:
- main.json - Primary site navigation
- footer-1.json - First footer column
- footer-2.json - Second footer column
Navigation items can be:
- Single links - Direct link to a page or external URL
- Dropdowns - Menu with multiple links
Links can reference internal pages by pageRef (using the page's permalink) or external URLs.
Categories are defined in src/content/categories/ and control filtering/grouping:
| File | Used For | Values |
|---|---|---|
blog.json |
Blog post categories | Technology, Development, Design, Product, Business, Tutorial, News, Opinion |
people.json |
Team member sections | Maintainers, Experiment Review Committee, Advisory Committee, M-Lab Founders |
partners.json |
Partner groupings | Supporting Research Projects, Supporting Partners |
publications.json |
Publication types | paper, regulatory-filing, presentation, documentation |
- Hosting: Netlify
- Auto-deploy: Pushes to
mainbranch trigger automatic deployment - Redirects: Configured in
src/content/site/_redirects.json
Pages CMS is the recommended way to edit content. It provides a visual interface that makes editing easier and reduces the chance of syntax errors. However, all content can also be edited directly in the repository files.
- Go to app.pagescms.org
- Log in with your GitHub account
- Select the Measurement Lab repository
- Edit content through the visual interface
- Changes are committed directly to Git
CMS configuration is defined in .pages.yml at the project root.
You can also edit content files directly in the repository. This is useful for:
- Bulk changes
- Complex edits not supported by the CMS
- Working offline
This section provides templates and examples for directly editing content files.
Create a new .md file in src/content/blog/:
---
permalink: my-new-post
title: My New Blog Post
excerpt: A brief description of the post
authors:
- chris-ritzo
published: published
tags:
- research
- data
categories:
- News
publishedDate: 2025-01-15
---
Your markdown content here...Required fields:
permalink- URL slug (no leading slash)title- Post titleauthors- Array of people IDs fromsrc/content/people/published- Eitherdraftorpublishedtags- Array of tag stringspublishedDate- Date in YYYY-MM-DD format
Optional fields:
excerpt- Short description for previewscategories- Array from: Technology, Development, Design, Product, Business, Tutorial, News, OpinionheroImage- Path to hero imageexternalAuthors- Comma-separated names for non-M-Lab authorsrelatedPosts- Array of up to 3 blog post permalinks
Create a new .yaml file in src/content/pages/:
title: My New Page
permalink: my-page
sections:
- type: hero
title: Page Title
zigzag: primary-light
background:
color: primary-medium
- type: richText
background:
color: white
withTOC: true
content: |
# Main Heading
Your markdown content here...
## Subheading
More content...
- type: people
category: Maintainers
background:
color: gray
- type: blog_roll
title: Latest News
limit: 3
showMore: trueCreate a new .json file in src/content/people/:
{
"id": "jane-smith",
"name": "Jane Smith",
"headshot": "/src/assets/people/jane-smith.jpg",
"title": "Research Director",
"affiliation": "Measurement Lab",
"sections": ["Maintainers"]
}Required fields:
id- Unique identifier (should match filename without extension)name- Display nameheadshot- Path to headshot imagesections- Array from: Maintainers, Experiment Review Committee, Advisory Committee, M-Lab Founders
Optional fields:
title- Job titleaffiliation- Organization nameextraInfo- Additional infourl- Personal/professional URL
Create a new .json file in src/content/partners/:
{
"id": "example-partner",
"name": "Example Partner",
"url": "https://example.com",
"category": "Supporting Partners",
"image": "/src/assets/partners/example.png",
"order": 10
}Required fields:
id- Unique identifiername- Organization namecategory- Either "Supporting Research Projects" or "Supporting Partners"
Optional fields:
url- Partner websiteimage- Path to logo imageaffiliation- Additional affiliation infoorder- Sort order (lower numbers appear first, default 999)
Create a new .json file in src/content/publications/:
{
"id": "2025-example-paper",
"title": "Example Research Paper",
"description": "Brief description of the publication",
"authors": "Smith, J., Johnson, A., Williams, B.",
"contributors": ["jane-smith"],
"year": 2025,
"category": "paper",
"venue": "ACM SIGCOMM",
"publishedDate": "2025-03-15",
"externalLinks": [
{
"label": "PDF",
"url": "https://example.com/paper.pdf"
}
],
"tags": ["research", "measurement"]
}Required fields:
id- Unique identifiertitle- Publication titleyear- Publication yearcategory- One of: paper, regulatory-filing, presentation, documentation
Optional fields:
description- Brief summaryauthors- Citation string for all authorscontributors- Array of people IDs fromsrc/content/people/(for M-Lab team members)venue- Conference/journal namepublishedDate- Full dateinternalLinks- Array of{label, path}for internal site linksexternalLinks- Array of{label, url}for external linksvideoLinks- Array of{label, url, platform}for video contenttags- Array of tag stringsorder- Sort order
Create a new .md file in src/content/tests/:
---
permalink: /tests/my-test/
title: 'My Test'
description: 'Brief description of the test'
status: current
icon: /src/assets/images/tests/my-test.png
order: 10
showInIndex: true
---
# My Test
Full markdown documentation for the test...Required fields:
permalink- URL path (should start with/tests/)title- Test name
Optional fields:
description- Brief descriptionstatus- One of: current, retired, core-service, retired-core-serviceicon- Path to icon image for tests indexorder- Sort order (default 999)showInIndex- Whether to show on tests index page (default true)parentTest- Parent test permalink for nested tests
Edit src/content/navigation/main.json:
{
"slug": "main",
"title": "Main Navigation",
"items": [
{
"type": "single",
"link": {
"type": "internal",
"label": "About",
"pageRef": "about"
}
},
{
"type": "dropdown",
"label": "Resources",
"links": [
{
"type": "internal",
"label": "Publications",
"pageRef": "publications",
"description": "Research papers and presentations"
},
{
"type": "external",
"label": "GitHub",
"externalUrl": "https://github.com/m-lab"
}
]
}
]
}Edit src/content/site/config.json:
{
"title": "Measurement Lab",
"description": "Site description for SEO",
"url": "https://measurementlab.net",
"favicon": "/favicon.svg",
"defaultOgImage": "/src/assets/og-image.png",
"defaultLogoLight": "/src/assets/logo-light.svg",
"defaultLogoDark": "/src/assets/logo-dark.svg",
"social": {
"github": "https://github.com/m-lab",
"x": "https://x.com/measurementlab",
"linkedin": "https://www.linkedin.com/company/measurementlab"
},
"footer": {
"description": "Footer description text (supports markdown links)",
"bottom": "Copyright and license text (supports markdown links)"
}
}- Define the schema in
src/content/config.ts - Create the collection folder in
src/content/ - Add category definitions in
src/content/categories/if needed - Update Pages CMS config in
.pages.ymlfor visual editing
- Create component in
src/components/sections/ - Register in
src/components/sections/Sections.astro - Add to
sectionsSchemainsrc/content/config.ts


