A guide for building the SUMA website
This guide was written for the website of the Swiss ungulate management project (SUMA), but it works for any project site. It describes what we did for popecol.org, how to set up the same for a new site, how to edit it by hand in RStudio, and how a project database can sit behind it without the data ever leaving our own server.
How popecol.org works
In October 2026 popecol.org moved from WordPress to a much simpler setup, in about a week, mostly by asking Claude Code to do the work and checking the result:
- Every page is a text file. The site is a Quarto website: pages are
.qmdfiles (Markdown, with R code where a page needs it), and lists such as publications, people and courses are YAML files indata/, from which R builds the pages. - GitHub keeps everything. All files live in one GitHub repository. Every change is a commit, so nothing is ever lost and any change can be undone.
- Publishing is automatic. Every push to the
mainbranch starts a GitHub Action that renders the site and publishes it on GitHub Pages, about three minutes later. The domain points to GitHub Pages; there is no server to maintain and nothing to pay for hosting. - Claude Code does most of the editing. Claude reads the repository, makes the change, renders the page to check it, commits and pushes. Two files make this reliable from session to session:
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).
The same pieces work for a project site. 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). Arpat can create it in his account and add you as a collaborator, or you create it and add him. A private repository can be published on GitHub Pages only with a paid GitHub plan (popecol.org’s is); a public repository works with a free one. The website content is public anyway; the data never goes 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 (next section). 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>/.
Setting up the site
You can do all of this in one or two sessions with Claude Code. In order:
- Create the empty repository on GitHub and open a Claude Code session on it.
- 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 that pages stay consistent and are easy to update. - Ask Claude to write
CLAUDE.mdand_docs/LOG.mdright away, and to keep them up to date. This is what makes the next session as good as the first. - Turn on GitHub Pages in the repository settings (source: GitHub Actions). From then on every push publishes.
- Move 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
.qmdpages, downloads and resizes the images, and keeps the old addresses working. - 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 does the same if CLAUDE.md asks for it.
Editing by hand in RStudio
Claude and you 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:
- 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.
- 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:
- 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. Do this every time, before you touch any file.
- Edit the
.qmdor.ymlfiles. - Preview. Click Render (or run
quarto previewin the Terminal tab) and check the page in the viewer. - 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. Do not commit the
_site/or_freeze/folders;.gitignoreshould already exclude them. - Push with the green up arrow. The site is live about three minutes later.
If the push is refused (“updates were rejected”), someone pushed in the meantime: pull, then push again. If Git reports a conflict, the same lines were changed on both sides; 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.”
From the group server: the same works 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.
Claude in RStudio: with Claude Code installed on your computer, type claude in RStudio’s Terminal tab. It then works in the same folder you have open, and you see its changes appear in the Git tab before anything is pushed.
Prompts for Claude
Claude works best with a clear goal, the reason behind it, 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
.qmdper page, lists (team, publications, partners) as YAML files indata/that R turns into pages, and a GitHub Action that renders and publishes to GitHub Pages on every push tomain. Use a light, calm design that works on phones. Then write aCLAUDE.mddescribing the structure and conventions, and start_docs/LOG.md. Render everything, check each page at phone width, and push.
Move 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 analiases:entry for each old address so old links still work. List anything you could not convert (forms, embedded maps, plugins) in_docs/LOG.mdinstead of guessing. Render the site and check for broken links before pushing.
Add something with a check:
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 toCLAUDE.md.
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 writesdata/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.mdand 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”).
A project database behind the site
The SUMA data (harvest statistics, counts, culling plans, fallen game, perhaps GPS locations) come from the cantons, which set conditions on how they are stored and who sees them. The setup we recommend keeps 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 to be published.
- The database lives on our own group server at UZH, in Zurich, run by Tim Vink. Raw data never go into the GitHub repository, never into a Slack message, and never into a Claude prompt.
On the group server
- Database: PostgreSQL with the PostGIS extension, which handles management units, hunting areas and coordinates well. Tim sets it up on the group server; each person gets their own login, and each cantonal dataset can have its own access rights.
- Access: only from the UZH network or the UZH VPN (or the group’s private network, set up by Tim), not from the open internet.
- Analyses: in RStudio Server on the same machine, so data never need to be copied to laptops. R connects with the
DBIandRPostgrespackages; connection details stay in a personal.Renvironfile, not in any script. - Backups and a record of who changed what: 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 a spelling such as “Rehwild”, “Reh” or “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), that reads the canton’s file as delivered, checks it and loads it; 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 are what goes into the website’s data/ folder; the site builds its charts and tables from them, exactly 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 than the public summaries (wildlife offices looking at their own canton in detail), a password-protected app on the group server, for example a Shiny app in R, can show finer data to named users only, from the same database. This would need Tim’s help to make it reachable from outside UZH.
What to check with the data providers
The cantons’ requirements are in each data agreement; read them first. Questions that usually come up, and how this 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.
- 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 that the database, logins and backups are in place, and to Arpat before anything is published.
Who to ask
- The website setup, GitHub, Claude Code: Arpat.
- The group server, the database, access from outside UZH: Tim Vink, in the #intranet channel on Slack.
- Data agreements and data protection: the cantonal data providers and the UZH data protection office.