Skip to main content

A guide for building the SUMA website

Building a project website like popecol.org with Quarto, GitHub and Claude Code, and keeping its data on our server.
Last updated

October 23, 2026

This guide is for the website of the Swiss ungulate management project (SUMA), but most of it works for any project site. It explains how popecol.org works, how to set up the same for a new site, how to edit it yourself in RStudio, and how a project database can sit behind it while the data never leave our own server. You don’t need to be a web developer: Claude does most of the work, and you check it.

How popecol.org works

In October 2026 popecol.org moved from WordPress to a much simpler setup. It took about a week, mostly by asking Claude Code to do the work and checking it.

Every page is a text file. The site is a Quarto website: pages are .qmd files (Markdown, with R code where a page needs it), and lists such as publications, people and courses are YAML files in data/, from which R builds the pages.

All files live in one GitHub repository. Every change is a commit, so nothing is ever lost and any change can be undone.

Publishing happens by itself. Every push to the main branch starts a GitHub Action that renders the site and publishes it on GitHub Pages, about three minutes later. The domain points to GitHub Pages, so there is no server to look after and nothing to pay for hosting.

Claude Code does most of the editing: it reads the repository, makes the change, renders the page to check it, commits and pushes. Two files keep it on track from one session to the next:

  • CLAUDE.md, at the top of the repository: who the site is for, how it is built, where everything is, and the house rules (spelling of years, how people are linked, what needs approval). Claude reads it at the start of every session. Each time you settle a question (“always link former members to their publication list”), ask Claude to add it there.
  • _docs/LOG.md: a short dated diary of what each session changed and what is still pending.

Anyone in the group can ask for a change by mentioning @Claude in the #website channel on Slack. A scheduled Claude routine checks the channel every hour (see the guide for updating popecol.org).

A project site can use the same pieces. You can leave out the Slack routine at first and add it later.

What you need

  • A GitHub account, and a repository for the site (for example suma-website). It can live in Arpat’s account with you as a collaborator, or the other way round. A private repository can be published on GitHub Pages only with a paid GitHub plan (popecol.org’s is); a public one works with a free plan. The website content is public anyway, and the data never go into this repository (see below).
  • A Claude account that includes Claude Code. You can use it in the browser at claude.ai/code, connected to GitHub, or on your own computer in a terminal.
  • RStudio with Git, for editing by hand (see below). Quarto comes with RStudio.
  • Later, a domain name, for example a short address for the project. Until then the site lives at <account>.github.io/<repository>/.

Set up the site

You can do all of this in one or two sessions with Claude Code:

  1. Create the empty repository on GitHub and open a Claude Code session on it.
  2. Describe the site and let Claude build the skeleton: pages, menu, colours, the publishing workflow. The first prompt below is a good start. Ask for the same structure as popecol.org (YAML files in data/ for anything that is a list), so pages stay consistent and are easy to update.
  3. Ask Claude to write CLAUDE.md and _docs/LOG.md right away, and to keep them up to date. This is what makes the next session as good as the first.
  4. Turn on GitHub Pages in the repository settings (source: GitHub Actions). From then on every push publishes.
  5. Bring over existing content. If the project already has pages elsewhere (a WordPress site, Word documents, a Google Doc), export them and give them to Claude. It converts them to .qmd pages, downloads and resizes the images, and keeps the old addresses working.
  6. Point the domain to GitHub Pages once the site looks right. The registrar needs four A records and a CNAME for www. Claude can tell you the exact values, and GitHub issues the HTTPS certificate by itself.

Work in small steps: one change, render, look at it, push. Claude works the same way if CLAUDE.md asks it to.

Edit the site yourself in RStudio

You and Claude can both work on the same repository. Git keeps the two in step, as long as you pull before you start and push when you finish.

Once, to get the site onto your computer:

  1. In RStudio: File › New Project › Version Control › Git. Paste the repository address from GitHub (the green Code button), choose a folder and click Create Project.
  2. The first time you push, RStudio asks for your GitHub name and a password: use a personal access token instead of your GitHub password (or set up an SSH key once, which RStudio can create under Tools › Global Options › Git/SVN).

Every time you edit:

  1. Pull. In the Git tab (top right), click the blue down arrow. This brings in whatever Claude or anyone else has changed since your last session. Always do this before you touch any file.
  2. Edit the .qmd or .yml files.
  3. Preview. Click Render (or run quarto preview in the Terminal tab) and check the page in the viewer.
  4. Commit. In the Git tab, tick the changed files, click Commit, write one line saying what you changed (“Team page: add Lea’s photo”), and commit. Don’t commit the _site/ or _freeze/ folders; .gitignore should already leave them out.
  5. Push with the green up arrow. The site is live about three minutes later.

If the push is refused (“updates were rejected”), don’t worry: someone pushed in the meantime. Pull, then push again. If Git reports a conflict, the same lines were changed on both sides, and RStudio marks them in the file with <<<<<<< and >>>>>>>. Keep the right version, delete the markers, commit and push. Or ask Claude: “I have a merge conflict in about/index.qmd; resolve it, keeping my wording.”

You can do the same in RStudio Server on our group server (see the welcome package), so you can edit from any computer on the UZH network without installing anything.

With Claude Code installed on your computer, you can also type claude in RStudio’s Terminal tab. It then works in the folder you have open, and you see its changes appear in the Git tab before anything is pushed.

What to ask Claude

Claude works best when you tell it what you want, why, and what “done” looks like. Some examples to adapt:

Start the site:

Set up a Quarto website for the SUMA project (evidence-based management of roe deer, red deer, chamois and wild boar in Switzerland, with the cantons of St. Gallen, Fribourg and Basel). Pages: Home, About the project, Species, Cantons, Team, Publications, News, Contact. English first; plan for German and French versions of the main pages later. Keep it as simple to maintain as popecol.org: one .qmd per page, lists (team, publications, partners) as YAML files in data/ that R turns into pages, and a GitHub Action that renders and publishes to GitHub Pages on every push to main. Use a light, calm design that works on phones. Then write a CLAUDE.md describing the structure and conventions, and start _docs/LOG.md. Render everything, check each page at phone width, and push.

Bring over existing content:

The attached file is a WordPress export (XML) of the old project site. Convert every published page and post into Quarto pages in this repository: keep titles, dates and authors, download the images into media/, resized to 1,600 px wide, and add an aliases: entry for each old address so old links still work. List anything you could not convert (forms, embedded maps, plugins) in _docs/LOG.md instead of guessing. Render the site and check for broken links before pushing.

Add something, and check it first:

Add a news post about our new paper (DOI attached): written by the group in its own words, not as a press release; what we found and why it matters for wildlife managers, in plain words; link the team members and the canton pages. Date it a week after the paper’s publication date. Use a photo only if it is openly licensed, and credit it in the caption. Render the post and show me the text before you push.

Keep the site consistent:

Go through the whole site and list anything that is inconsistent with CLAUDE.md: spellings of species and canton names, year ranges, links to team members, image sizes, pages missing a description. Fix the plain mistakes in one commit, list the doubtful ones for me, and add any new rule we agree on to CLAUDE.md.

Show public numbers from the database:

Write an R script, scripts/export-public.R, that connects to the SUMA database on the group server (connection details from environment variables, never in the repository), and writes data/harvest-summary.csv: total harvest per species, canton and year, nothing finer than canton level, and no cell based on fewer than 5 records. Then add a page “Harvest in the three cantons” with one chart per species built from that file. The script must never write raw records, coordinates or names of hunters or data providers.

Look before changing:

Before changing anything: read CLAUDE.md and the last entries of _docs/LOG.md, look at how the Team page is built, and tell me in a few lines how you would add a “Partners” page with the logos of the cantonal hunting offices. Wait for my go before you start.

Two habits help: say who the page is for (“wildlife managers in the cantons”, “the public”), and say what Claude must not do (“do not change the design”, “do not delete anything”, “show me before you push”).

Keep the data on our own server

The SUMA data (harvest statistics, counts, culling plans, fallen game, perhaps GPS locations) come from the cantons, and they set conditions on how the data are stored and who sees them. So we keep two things strictly apart.

The website is public and lives on GitHub Pages, whose servers are not in Switzerland. It only ever contains what may be public: text, figures, and summary tables that the data agreements allow us to publish.

The database lives on our own group server at UZH, in Zurich, run by Tim Vink. Raw data never go into the GitHub repository, a Slack message or a Claude prompt.

What runs on the group server

The database is PostgreSQL with the PostGIS extension, which handles management units, hunting areas and coordinates well. Tim sets it up. Each person gets their own login, and each cantonal dataset can have its own access rights.

It can only be reached from the UZH network or the UZH VPN (or the group’s private network, set up by Tim), not from the open internet.

Analyses run in RStudio Server on the same machine, so the data never need to be copied to laptops. R connects with the DBI and RPostgres packages, and the connection details stay in a personal .Renviron file, not in any script.

Backups, and a record of who changed what, are still to agree with Tim.

How the data could be organised

One table per kind of record, each row tagged with where it came from and under which conditions it may be used:

Table One row per… Main fields
provider data provider (canton, office) name, contact, agreement, date of agreement
agreement data agreement provider, what may be published, at what level, embargo, end of use
area management unit or hunting area canton, name, type, boundary (PostGIS polygon)
harvest animal shot species, sex, age class, date, area, weight, provider
count count or survey species, method, date, area, number seen, provider
fallen_game animal found dead species, cause (traffic, disease, …), date, area or location, provider
plan culling plan species, canton, year, planned and achieved numbers

Species, cantons, methods and causes are best kept as short lookup tables, so that “Rehwild”, “Reh” and “roe deer” can never split one species into three. Each import from a canton is a script, kept in a separate private repository (never the website’s). It reads the canton’s file as delivered, checks it and loads it, and the original files stay unchanged on the server.

From the database to the website

A script on the server (the “public numbers” prompt above) reads the database and writes only the summaries the agreements allow, for example harvest per species, canton and year. Those summary files go into the website’s data/ folder, and the site builds its charts and tables from them, just as popecol.org builds its publication list from publications.yml. Before the first publication, each canton should see and approve exactly what will be shown.

For partners who need more detail (wildlife offices looking at their own canton), a password-protected app on the group server, such as a Shiny app in R, can show finer data to named users only, from the same database. Making it reachable from outside UZH would need Tim’s help.

Questions the cantons will ask

Read each data agreement first. These questions usually come up, and this is how our setup answers them:

  • Where are the data stored? On a UZH server in Zurich, Switzerland; not in the cloud, not on GitHub.
  • Who can see them? Only named people with a personal login, from the UZH network; access can be limited per canton.
  • What is published? Only summaries at the level the agreement allows, approved by the canton; never raw records, exact locations or personal data such as names of hunters.
  • Can data be deleted at the end of the project? Yes. Each record carries its provider, so one canton’s data can be removed completely.
  • What about personal data? If any records name people (hunters, game wardens), the revised Swiss Federal Act on Data Protection (nFADP) applies. Keep such fields out of analyses where possible, and check with the UZH data protection office.

Talk to Tim before the first data arrive, so the database, logins and backups are ready, and check with us before anything is published.

Who to ask

For the website setup, GitHub and Claude Code, ask Arpat. For the group server, the database and access from outside UZH, ask Tim Vink in the #intranet channel on Slack. For data agreements and data protection, ask the cantonal data providers and the UZH data protection office. When in doubt, ask me.