← Alle Beiträge

Rebuilding an Artist’s Portfolio with Hugo

My brother Nils Karsten is an artist based in New York. For years his website ran on Indexhibit, a classic portfolio CMS that had become tied to PHP 7.4. It received no updates, its markup was dated and it offered almost no SEO. Rather than patch it again, I rebuilt the site from scratch as a static site with Hugo Extended (0.168). It has been live at nilskarsten.com since the end of October 2026.

Why static

A portfolio changes a few times a month, not a few times a minute. With Hugo there is no database and no PHP runtime left to maintain, and the attack surface shrinks to the web server itself. Every artwork series lives in a page bundle together with its images. Hugo’s image processing generates responsive WebP variants (480 to 1600 px, plus a larger one for the lightbox) with srcset/sizes at build time, so the originals from the studio can go in unchanged.

All addresses of the old site were kept – only the /index.php part is gone, and the old URLs redirect with a 301.

One page, many tags

The woodcut work used to be split into several sub-series. The artist preferred a single page arranged by visual relationships instead of by theme. So every image now carries tags in its front matter (several per work are allowed), and a small client-side filter written without any library shows or hides the works, loading more in steps of 24. The active filter is kept in the URL (/work/woodcuts/#history), so filtered views can be linked. The old series URLs redirect to the matching filter with a 301 in .htaccess. A parameter in hugo.toml switches the filter on or off.

Environments instead of manual edits

There are two targets: a staging server for reviews and the live site. Staging is built with --environment staging, which sets noindex on every page and writes a robots.txt with Disallow: /. Production allows indexing and writes the sitemap. Features such as the randomly chosen hero image on the homepage were first enabled on staging only and went live through the configuration, without any template changes.

The builds go to separate folders:

1
2
hugo --gc --minify --cleanDestinationDir --destination _live
hugo --environment staging --gc --minify --cleanDestinationDir --destination _staging

There is a reason for this. Early on, a running hugo server silently overwrote public\ with a build pointing to the local preview address, and that build got uploaded. Since then public\ is used only for the local preview, and the upload is a WinSCP sync from _live\ with removal of stale files. Before each upload, a short check counts references to the preview address and the staging domain in the output (both must be zero).

SEO

  • description and SEO title per page, JSON-LD for the artist and the works
  • Alt texts generated from the work title and caption
  • Descriptive image filenames and an image sitemap with lastmod
  • og:image, twitter:card, the hero image loaded eagerly, a 404 page without canonical
  • HTTPS redirect and Brotli compression handled by the host

Matching images against the catalogue

The artist keeps a catalogue of works as a PDF. To match the website images with their titles and dimensions, I extracted the images with pdfimages and the captions with pdftotext. The two were then matched with OpenCV feature matching (ORB + RANSAC). Real matches had hundreds of inliers and false matches fewer than 50, which made the assignment straightforward.

Benefits

  • No server-side runtime, no CMS updates, minimal attack surface
  • Fast pages with responsive WebP images
  • A safe review loop through a separate staging build
  • Linkable filtered views instead of rigid sub-series
  • Clean metadata for search engines and social previews
#hugo#static-site#seo#portfolio#hosting
Diese Website verwendet keine Cookies. Weitere Informationen finden Sie in der Datenschutzerklärung.