Articles
Drupal

How WebPro built its website — custom build, AI-assisted, honest review

The WebPro website is not Drupal and not a ready-made framework. It is a Node.js static generator written from scratch, with most of the code written by AI. This is an honest account of how that happened and what it actually meant.

Why not Drupal

Drupal is what WebPro works with every day. But Drupal makes sense where there is complex content management logic, multiple user roles, integrations or a need for non-technical people to manage content. The WebPro website has none of those needs. There are about a dozen pages, no content editors, and content changes infrequently.

Drupal would have brought all its obligations along — security updates, modules, a database, a deployment process — with nothing to gain. It would have been the wrong tool.

Why not a ready-made framework

The next question was whether to use an existing static site generator — Hugo, Jekyll, Next.js, Astro. The answer was no, because each one brought its own abstraction layer, configuration logic and dependencies. It was simpler to write it directly — Node.js scripts that read Markdown files and output HTML. That gives full control over what happens and eliminates framework version conflicts.

This is a custom solution in the full sense — no intermediate layer whose behaviour needs to be guessed.

AI assistance — what that actually meant

Most of the code was written by AI — both Claude (Anthropic) and Codex (OpenAI) were used. The generator scripts, component system, test suite, .htaccess rules, PHP contact form. This does not mean AI did everything and the work was easy. It means AI was a developer whose output had to be checked, debugged and directed every step of the way.

Architectural decisions — what to build in the first place, how to handle bilingual content, how to structure the tests — remained for a human to make. AI is fast at writing code but does not know which approach is right when there are multiple options.

Honestly: without AI assistance this project would have taken several times longer. But even with AI assistance it took longer than initially expected.

What was actually hard

The most time-consuming part was the Apache .htaccess mod_rewrite logic. The problem: a /tood directory and a tood.html file exist simultaneously. Apache automatically redirects /tood to /tood/, which conflicts with another redirect rule. The solution was adding an explicit rule that catches /tood and /tood/ before Apache triggers its default behaviour. The fix is two lines, but finding it took several hours.

The second thing that proved harder than expected: the test suite. Tests check every page's contact section, meta tags, JSON-LD schema, hreflang links, robots.txt behaviour and basic WCAG compliance. Writing the Playwright tests took more time than writing the generators. But they are also what prevents a change from silently breaking something.

What arrived unexpectedly

The Drupal scanner — a public tool at /drupal-audit — was initially intended as just one page. It grew into a ~2,400-line PHP file that analyses third-party Drupal sites against 40+ risk indicators. That happened the same way: Claude and Codex wrote most of the code, a human defined the logic and validated the output.

Auto-deploy — an Apache and SSH lesson

Adding content is a single file change. But how does that actually reach the server?

The solution has two parts. The npm run deploy command does two things at once: pushes changes to Bitbucket and immediately SSH-pulls the new version on the server. In addition, every git push triggers a Bitbucket webhook that calls a PHP script on the server, which runs git pull — fully automatically.

In practice it did not work immediately. Apache's PHP-FPM process sees a different HOME environment variable than the SSH user on the same server. Git uses SSH; SSH looks for key files in $HOME/.ssh/ — but the HOME seen by the Apache PHP process pointed to a different directory. The error was Host key verification failed, meaning SSH could not find Bitbucket in its known hosts list.

The fix: a GIT_SSH_COMMAND environment variable that explicitly specifies the SSH key file and known_hosts location — both as absolute paths derived from __DIR__. Two lines of PHP, several hours of debugging.

Social media images — 59 images from nine lines of config

When a post or service page is shared on LinkedIn or Facebook, the platform shows a preview image. By default every page used the same generic image — the WebPro logo on a white background. That does not distinguish pages from each other or give the person sharing any context.

The solution: during each build, a unique 1200×630 pixel PNG is generated for every page. The script is Python and Pillow, runs as part of npm run build.

The image design is straightforward: a white left panel with the page title, a red right panel with a category label. The label text comes directly from metadata already in the content files — blog posts use the tag field (tag: GDPR), case studies use the service field (service: Migration). Every image is contextually distinct with no extra manual work.

One design problem had to be solved: short labels like "AI" would look lost on an empty panel at a fixed font size. The fix: the font scales to the content — it tries sizes 108px, 88px, 72px, 58px and smaller until the text fits the panel. Multi-word labels such as "Custom development" wrap to two lines automatically.

59 images, all automatic, all contextually relevant — added in roughly half a day of work.

Lighthouse: 100/100/100/100

Lighthouse is Google's tool for measuring web pages across four dimensions: performance, accessibility, best practices and SEO. The WebPro site scores 100 on all four — tested on a slow 4G network with a mobile device.

Performance comes from architecture. Files are served directly from the filesystem — PHP doesn't execute on page load, there is no database. CSS is inlined in the <head>, not in an external file. First Contentful Paint is 0.8 seconds. Layout shift is zero — every element has its dimensions set.

SEO is thought through per page: canonical URL, unique description, Open Graph tags, JSON-LD structured data (ProfessionalService, BlogPosting, WebPage), hreflang cross-links between ET and EN, sitemap.xml, robots.txt.

Accessibility is covered at two levels. First structurally — skip link, logical heading hierarchy, alt text, WCAG 2.1 AA colour contrast. Second, in the automated test suite — Playwright and axe-core run on every build.

LLM visibility. Today ChatGPT, Claude and other AI tools answer questions — not just Google. robots.txt explicitly allows OAI-SearchBot, GPTBot and ChatGPT-User. llms.txt is a separate file for AI tools: a structured description of what WebPro does, a list of important pages and the questions the site answers.

An experiment in its own right: only what we actually need

This project is also an experiment in its own right — building a complete solution exactly as we need it, with no unnecessary code. Every component is there because it is needed, not because a framework brought it along. If something is not needed, it is not in the codebase.

This is a different approach from a typical web project, where you pick a CMS, install a theme and start adding plugins. Here everything is written for a specific purpose — and because of that, everything is also manageable.

This is not finished

The site evolves over time. Blog articles, case study write-ups, new tools — all of it is added as it comes. Each new piece of content is adding one Markdown file — no database migration, no plugin update, no deployment process beyond a file change.

That is also why choosing this approach paid off. Not because building it was easier — it was not. But because adding and changing things afterwards is easy.

What the result is

The site works. It loads quickly because files are served directly from the filesystem without a cache layer. Updating content is a change to one file. Security maintenance applies to two PHP files only.

Is this the right approach for everyone? No. It is the right approach when there is enough technical competence to write and maintain a custom solution, and when there is no need for content management logic. For WebPro, both conditions were met.

Drupal remains the main tool for client work. On our own site it has not been needed.

Kaido Toomingas Kaido Toomingas WebPro Company OÜ

Need Drupal help?

If the article describes your situation, you do not have to read everything first. A real person will help you choose the next step.