🧪

DevUnit Lab: A Bilingual Studio Website on Nuxt 4

our own studio site, in production since 29 September 2026

Our own studio website on Nuxt 4 and Nuxt Content 3: content in markdown with no database, Russian and English versions, JSON-LD markup, a 52-term IT glossary, a case catalogue and a brief form.

In production · in-house product
RU / EN
two language versions with language detection and hreflang
52 terms
a glossary in each language with DefinedTermSet markup
1 container
no database and no admin: content in git, 150 to 300 MB of memory
Home page of the DevUnit Lab studio website: headline, buttons and the container diagram
Home page of the DevUnit Lab studio website: headline, buttons and the container diagram

The task

The previous studio site was one long page: service areas and cases had no URLs of their own, and therefore no search queries of their own. The studio needed a site where every service area has its own page, every case has its own URL, and there is an English version and a glossary for clients who are far from IT.

Additional conditions:

  • content is edited as text files in git, with no admin panel and no database;
  • clients under NDA are anonymised, while our own products are named;
  • price lists switch on with a single flag once they are written;
  • the site opens fast on a phone from an ad link.

The solution

A multi-page site on Nuxt 4 and Nuxt Content 3 with server-side rendering. Nuxt Content is a module that turns markdown and YAML files into site pages and validates their structure against a schema, so a typo in a field never reaches a visitor.

What was implemented:

  • Home page: headline, key figures, service areas, featured cases, work stages, the stack by group, common questions and answers, and a brief form.
  • A page for each of the five service areas: lead, stack, its own questions and answers, and the area's cases. The case list is built from the direction field, with no hand-made lists.
  • A case catalogue with a filter in the URL; a case page shows the status, an NDA or «own product» label, result tiles, the stack and related cases.
  • Russian and English versions with language detection and a switcher.
  • A glossary of 52 IT terms in each language; badges on technologies link to it with a hint.
  • A chat widget and a brief form on the home page.
  • Light and dark themes, floating blocks, a cursor glow, blocks revealing on scroll and a bottom navigation bar on phones.
  • Yandex Metrica with goals and a cookie notice; both switch on only when a counter number is set.

How it works

Content in markdown instead of a database

Cases, service areas, the glossary and shared data live in the content/ folder as markdown, YAML and JSON. The field schema is described with Zod and checked at startup: a case must have a subtitle, an emoji and a direction, and the status is chosen from a list. The English version lives in content/en/ under the same file names and with the same schema.

Nuxt i18n and hreflang for two languages

hreflang is a hint to a search engine that a page has a version in another language. Russian has no prefix and English uses /en. The @nuxtjs/i18n module picks the language from the browser, remembers the choice and puts lang and hreflang into every page head. Russian and English texts are stored in separate content collections.

Sitemap and robots.txt as server routes

sitemap.xml and robots.txt are built in code from the content collections, so a new case gets into the sitemap by itself, in both language versions. One environment variable switches a staging site to «do not index»: it adds meta robots noindex and a disallow rule in robots.txt.

JSON-LD structured data for search and AI answers

JSON-LD is data about a page in a format robots read: who we are, which questions we answer, what a term means. The home page carries Organization and WebSite, question-and-answer blocks carry FAQPage, and the glossary carries DefinedTermSet.

Captcha-free brief anti-spam

The brief goes to the /api/lead server route. It drops requests with a filled hidden field, requests sent less than four seconds after the form appeared, and texts with three or more links, and it rejects a fourth request within 10 minutes from one address. The bot gets «ok» in reply, so it is not told what failed. The per-address counter is kept in process memory and resets when the container restarts.

One container on the web server

The image is built locally, because the web server does not have enough memory to build Nuxt, and is uploaded ready-made with docker save. The container does not publish a port: only the front nginx sees it, through a dedicated Docker network. The container's time zone is set to Europe/Moscow and its memory is limited to 512 MB.

Results

  • Since 29 September 2026 the site has run on the main domain, and the old staging host redirects to it.
  • Every service area and every case has its own URL, and every page has its own title, description and markup.
  • The site runs without a database and an admin: content changes go through git.
  • A glossary of 52 terms is available in Russian and English.
  • The brief is protected from bots without a captcha and without extra steps for the visitor.

Technologies and why

  • Nuxt 4, Vue 3, TypeScript — server-side rendering, so search engines see ready text.
  • Nuxt Content 3 — content in markdown and YAML, structure checked against a Zod schema, queries over collections.
  • @nuxtjs/i18n — Russian and English, language detection, hreflang.
  • Tailwind CSS 4 — layout and our own design tokens in one place.
  • Docker — one container per web server, with the Europe/Moscow time zone.
  • Yandex Metrica — goals for contact clicks, the brief and the chat; connected only when a counter number is set, with a cookie notice next to it.

Status

Version 1.1.0 of 29 September 2026, in production at devunit-lab.ru. Still open: not all price lists are written, so the «Cost» block is hidden in the sections; cases have no real screenshots, only automatic covers. The repository has no automated tests.

The previous version

The previous site was a set of static pages in plain HTML, CSS and JavaScript with Tailwind CSS 3 for styles. It included a studio page in Russian and English with a brief, a chat and a glossary, an English-language site for a foreign audience, and a landing page called «Дверной ГОСТ» for an interior-door installer. History was kept by date from 30 July 2026, with no version numbers. The studio site in Russian and English has been replaced by the Nuxt version; the English-language site and the landing page are described in the repository as static.

Questions about this project

Can a studio website be built without WordPress and without a database?
Yes. The devunit-lab.ru site is built on Nuxt 4 and Nuxt Content 3: pages, cases and the glossary live in markdown and JSON files in git, and one container does the server-side rendering. Editing the site means editing a file and rebuilding. The container needs 150 to 300 MB of memory.
How do you build a bilingual site with correct URLs for search engines?
The Russian version opens with no prefix and the English one at /en. The @nuxtjs/i18n module detects the language from the browser on the first visit and remembers the choice in a cookie. Every page has hreflang, and the English content lives in a separate folder with the same file names.
What SEO markup does the studio site use?
Every page has its own title, description and Open Graph tags and a canonical URL, while the sitemap and robots.txt are generated by server routes from the content collections. JSON-LD markup: Organization and WebSite on the home page, FAQPage for questions and answers, DefinedTermSet for the glossary. In staging mode one environment variable closes the site to indexing.
Why does the site need an IT glossary?
It explains in plain words what Docker, self-hosted or n8n mean for a client. The glossary holds 52 terms in each language, and technology badges on the site carry a «What is it?» hint that links to the right term.
How does a request arrive from the site, and is the form protected from spam?
The brief is sent to the /api/lead server route, which passes it to a separate request-intake service; without that service the form runs in demo mode and suggests writing on Telegram. Protection without a captcha: a hidden trap field, rejection of submissions made under four seconds after the form appears, at most three requests per 10 minutes from one address, field length limits and rejection of texts with three links.
What did the studio site look like before Nuxt?
The previous site was a set of static pages in HTML, CSS and JavaScript, with styles compiled by Tailwind CSS 3. The Russian and English versions were one large file per language, and the chat and the brief called a service outside the repository. The new version kept the style and animations but gave every service area and every case its own URL.

Need something similar?

Tell us about the task — we'll show how we solved it and estimate the scope.