Xu Lab

Xu Lab Website

A bilingual (English/Chinese) research group website built with Hugo and the theme-research-group theme. Deployed to xulab.pages.dev via Cloudflare Pages.


⚠️ GUIDING PRINCIPLE

Keep the code and the content base minimal.

Resist the urge to add features, pages, customizations, or dependencies unless strictly necessary. Every addition has a maintenance cost. When in doubt, leave it out.


Local Preview

For local development and testing changes before pushing to github:

Prerequisites

Run the development server

python update_publications.py --local   # reads the running Zotero app; or set ZOTERO_API_KEY and omit --local
academic import --bibtex "Exported Items.bib" content/en/publication/ --overwrite --compact
academic import --bibtex "Exported Items.bib" content/zh/publication/ --overwrite --compact
hugo server

Open http://localhost:1313 in your browser. The server auto-reloads on file changes.

To customize theme templates, vendor the theme first:

hugo mod vendor

Then copy templates from _vendor/github.com/HugoBlox/hugo-blox-builder/modules/blox-bootstrap/v5/layouts/ into layouts/ and edit there. Prefer CSS over custom layout overrides — only copy a theme template if CSS cannot achieve the same result.


Architecture

Bilingual Content

All content exists in parallel under content/en/ and content/zh/. Changes to one language usually need a matching change in the other. Language config is in config/_default/languages.yaml.

The layouts/partials/blocks/people.html template implements a fallback mechanism: the Chinese site uses English author profiles as fallback, so only authors with actual Chinese translations need a content/zh/authors/<name>/ directory.

Configuration Files

Content Types

Custom Layouts (theme overrides)

Styling


Managing Content

Publications

The publication list lives in Zotero, not in this repo: the Publications collection of the xu-research-lab Zotero group. Every build fetches it fresh (update_publications.py → Exported Items.bib, untracked) and generates the pages under content/*/publication/ — do not edit those files manually.

When the site picks up changes:

To show/hide authors on the publications page, edit config/_default/hugo.yaml:

taxonomies:
  author: authors  # comment out to hide authors

Lab Members

Each member is defined by a folder under content/en/authors/<name>/ containing:

Author profiles follow a consistent Hugo Blox template format with these sections (commented out when empty): bio, interests, education, social.

The user_groups field determines which section the member appears in:

Set no_link: false to enable links to the member’s profile page, or no_link: true to disable.

Only create content/zh/authors/<name>/ if you have an actual Chinese translation (the English profile is used as fallback).

To remove a member: delete their folder from content/en/authors/ (and from content/zh/authors/ if a Chinese version exists).

To sort members: within each group, members are ordered by weight defined in their profiles. The weight value combines role priority (Postdoc = 30, Ph.D. = 20, M.S. = 10) with year. Use graduation year for alumni, or start year for current members, e.g., 102021. The sorting is configured in content/en/people/index.md.

Blog Posts

Each post is a folder under content/en/post/<slug>/ containing index.md.

To add a post: create the folder and index.md. Set draft: true to hide it from the live site. Mirror under content/zh/post/<slug>/ if a Chinese version is needed.

To remove a post: delete the folder from both language directories.

Activity Page (Photo Slider)

The slider is configured in content/en/activity/index.md and content/zh/activity/index.md.

To add a photo:

  1. Put the image file in assets/media/ (e.g. assets/media/group3.jpg)
  2. Add a slide entry in both activity files:
- title: "2025.9"
  content: Lab retreat
  align: end
  background:
    image:
      filename: group3.jpg
      filters:
        brightness: 1.0

To remove a photo: delete its slide entry from both files (and optionally delete the image from assets/media/).

Events

Events are listed at /event/. Each event is a folder under content/en/event/<slug>/ containing index.md.

A minimal event file:

---
title: "Talk: Title of the Talk"
event: Conference or Seminar Name
location: Room 503, Building X
date: 2025-09-01T14:00:00
date_end: 2025-09-01T15:00:00
all_day: false
abstract: "Brief description of the talk."
---

Mirror the file under content/zh/event/<slug>/ with a Chinese version.

Links are defined in content/en/_index.md and content/zh/_index.md as a markdown block:

- block: markdown
  content:
    title: Useful Links
    text: |
      - [**Link Name**](https://example.com)
        Brief description
  design:
    columns: '1'
    css_class: section-useful-links

Add, remove, or reorder items in the markdown list. Mirror the change in content/zh/_index.md.

Contact Page

Edit content/en/contact/index.md and content/zh/contact/index.md. The address, coordinates, and office details are set in the front matter of those files.

Edit config/_default/menus.yaml (English) and the menu.main section in config/_default/languages.yaml (Chinese). Each entry has a name, url, and weight (lower weight = appears first).

Homepage Background

Replace assets/media/true.jpg, or edit the design.background.image.filename field in content/en/_index.md and content/zh/_index.md.

Site Icon

Replace assets/media/icon.png (keep the filename).

Recruiting Information

Edit content/en/authors/we-want-you/_index.md (and content/zh/authors/we-want-you/_index.md) to reflect current open positions.


Password-Protected Posts

Specific posts are encrypted after the Hugo build using StaticCrypt. The encrypted HTML is what gets deployed — the plaintext is never served to the browser.

The CI step:

staticrypt public/en/post/lab-nas-guide/index.html -p "$HUGO_SITE_PASSWORD" -d public/en/post/lab-nas-guide/ --short

The --short flag suppresses the interactive confirmation prompt in non-interactive CI environments.

To protect an additional post, add a line in the same form to the “Encrypt protected pages” step in .github/workflows/publish.yaml.

The password is stored as a GitHub secret named HUGO_SITE_PASSWORD. To set or update it:

  1. Go to Settings → Secrets and variables → Actions in the GitHub repository.
  2. Click New repository secret.
  3. Set Name to HUGO_SITE_PASSWORD and Value to your chosen password.

CI/CD Workflow

Pushes to the master branch, a daily schedule, or a manual run trigger .github/workflows/publish.yaml, which:

  1. Runs update_publications.py to fetch the Zotero group’s “Publications” collection into Exported Items.bib.
  2. Runs academic import "Exported Items.bib" content/*/publication/ --compact to regenerate publication pages.
  3. Builds the site with Hugo (--minify --baseURL "https://xulab.pages.dev/").
  4. Encrypts password-protected pages with StaticCrypt.
  5. Deploys to Cloudflare Pages using wrangler pages deploy.

Cloudflare Pages Setup (one-time)

1. Create the Pages project

  1. Log in to the Cloudflare dashboard.
  2. Go to Workers & Pages → Create → Pages → Direct Upload.
  3. Name the project xulab and complete the wizard.

2. Find your Account ID

Navigate to Workers & Pages. The Account ID is the hex string in the dashboard URL:

https://dash.cloudflare.com/<ACCOUNT_ID>/workers-and-pages

3. Create an API Token

Go to My Profile → API Tokens → Create Token. Use the “Edit Cloudflare Workers” template, or create a custom token with the “Cloudflare Pages: Edit” permission.

4. Add GitHub Secrets

Go to Settings → Secrets and variables → Actions in the GitHub repository and add:

Secret name Value
CLOUDFLARE_API_TOKEN The API token from step 3
CLOUDFLARE_ACCOUNT_ID The Account ID from step 2
HUGO_SITE_PASSWORD The site password for protected posts
ZOTERO_API_KEY A read-only Zotero API key with access to the xu-research-lab group (zotero.org/settings/keys)

SEO & Search Engine Verification

The site’s search-engine verification and SEO signals live in four places:

Both verification files sit in static/ (not the repo root or public/) because Hugo copies static/ verbatim to the site root — that is the Hugo equivalent of “upload the file to your site root.”


Dependencies