Search by

A lightweight PHP engine for quickly launching simple websites: SEO, optional i18n, caching, tracing and form protection out of the box.

Package info

github.com/golovanov-dev/weft

pkg:composer/golovanov/weft

Statistics

Installs: 23

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-10-11 14:52 UTC

This package is auto-updated.

Last update: 2026-10-11 14:58:45 UTC


README

A lightweight PHP 8.4 engine for quickly launching simple websites — SEO, optional multilingual routing, caching, request tracing and form protection out of the box. You write pages and a little business logic; the engine handles the rest.

Weft is for content-first sites: landing pages, portfolios, product and documentation sites. It is deliberately not a full-stack framework — no ORM, no DI container, no admin panel.

<?php // pages/about.php  ->  /about
use Golovanov\Weft\Seo\Seo;

Seo::set(['title' => 'About', 'description' => 'What I do and how I work.']);
?>
<h1><?= t('about.title') ?></h1>
<a href="<?= e(url('/contact')) ?>"><?= t('nav.contact') ?></a>

That file is the whole route. No route table, no controller, no registration.

Why

Every small site re-solves the same problems: clean URLs, locale prefixes, a correct <head>, sitemap, spam-resistant forms, tracing, asset hashing. Weft puts all of it behind a small, convention-driven core so a new site is mostly content.

Features

  • Convention routing — pages/about.php → /about, api/contact.php → /api/contact, pages/notes/[slug].php → /notes/anything, pages/wiki/[...path].php → /wiki/any/depth. One address per page: /about/ and /home redirect, a malformed path is a 404.
  • SEO under the hood — title templates, canonical, Open Graph, Twitter cards, hreflang and JSON-LD rendered from config + per-page overrides; JSON-LD builders that check required fields, breadcrumbs and pagination with self-canonical pages. Generated /sitemap.xml and /robots.txt.
  • Visible to search engines and AI assistants — robots.txt rules per crawler with presets for AI search, user-triggered and training bots; /llms.txt; IndexNow submission of new and changed pages from cron; vendor/bin/weft-seo-audit checks a live site's pages, in CI too.
  • Optional i18n — off by default. Turn it on and the default locale stays prefix-free (/about), others get /ru/about, with automatic hreflang and canonical redirects.
  • Optional caching — file or Redis. Cache::remember() is safe even when caching is off.
  • Request tracing — one JSONL timeline per request via traceloom, with redaction of sensitive keys. Request bodies are logged by default, so what your forms collect (an email, a phone number, a message) is kept in the trace for app.trace.retention_days days (30 by default) unless you list its key in app.trace.sensitive_keys. Turning app.trace.log_body off drops bodies, not the fields your code records with Trace::event().
  • Form protection — rate limiting, CSRF, honeypot and a captcha (Google reCAPTCHA v3 or Yandex SmartCaptcha, off by default), called from your form's endpoint.
  • Mail — plain-text UTF-8 email through PHP mail() or SMTP (STARTTLS or TLS), no extra package.
  • Alerting for sensitive spots — to AlertLoop, Telegram, email or the error log; best-effort with a timeout, and a failed delivery is recorded on the request trace instead of vanishing.
  • Batteries in the engine — HTTP client, PDO wrapper, JWT (firebase/php-jwt), security headers.
  • Vite — dev server with HMR, hashed production assets from the manifest, with preload hints for the entry and its statically imported chunks.

Requirements

  • PHP 8.4+ with ext-curl and ext-json
  • ext-pdo with pdo_mysql only if you use the database (MySQL), ext-redis only for the Redis cache, ext-dom only for vendor/bin/weft-seo-audit
  • Node.js 20.19+ or 22.12+ (required by Vite 7) only to rebuild the frontend — not needed to run the example, whose built assets are committed

Install

Weft expects a small site skeleton around it. The fastest way to get one is to clone the repo and copy the example/ directory — it is a complete, working site, and its composer install installs the engine:

git clone https://github.com/golovanov-dev/weft.git
cp -r weft/example my-site
cd my-site
composer install

In my-site/.gitignore, delete the line /composer.lock, then make my-site a repository (git init) and commit the lock file with your site.

To add the engine to a site skeleton of your own instead, in its directory:

composer require golovanov/weft

Get the example by cloning, not by "Download ZIP". The package archive is kept lean: example/, tests/ and CI config are export-ignored, so composer require — and GitHub's ZIP / release "Source code" downloads — contain only the engine. git clone gives you everything.

Project layout

your-site/
├── app/
│   ├── Configs/app.php     business config, read via config('app.*')
│   ├── Bootstrap.php       site wiring
│   └── Services/           your business logic
├── config/config.php       environment constants (secrets) — not in git
├── pages/                  views → routes
├── api/                    JSON endpoints → /api/*
├── components/layout.php   layout (receives $content)
├── lang/<locale>/          translation arrays
├── frontend/src/           Vite sources
├── public/index.php        the only entry point (document root)
├── storage/                logs, cache, rate-limit counters (cache/ratelimit/)
└── bootstrap.php           shared init (web + CLI)

Quick start

No build step: the example's built assets are committed, so it runs straight after composer install.

cd example
composer install
php -S localhost:8000 -t public public/index.php

Open http://localhost:8000. The example is the Weft documentation site — it documents the engine while being built with it. With no config/config.php present, it falls back to config/config.example.php (DEV_MODE=false) and serves the committed assets.

Working on the frontend (optional)

Node is needed only to edit styles/JS:

cp config/config.example.php config/config.php   # then set DEV_MODE=true in it
npm install
npm run dev                                       # Vite dev server on :5173

npm run build refreshes the committed public/dist/.

Configuration in two layers

  • Environment — config/config.php, plain define() constants: secrets, DB/Redis credentials, DEBUG, DEV_MODE, APP_TIMEZONE. Per server, gitignored.
  • Business — app/Configs/app.php, an array read with dot keys: SEO, locales, feature flags, rate limits. Committed.
config('app.seo.site_name');
config('app.cache.enabled', false);

Everything optional is a flag in app/Configs/app.php:

'locales'   => ['enabled' => true, 'supported' => ['en', 'ru']],
'cache'     => ['enabled' => true, 'driver' => 'redis'],
'captcha'   => ['enabled' => true, 'driver' => 'recaptcha', 'site_key' => CAPTCHA_SITE_KEY, 'secret_key' => CAPTCHA_SECRET_KEY],
'mail'      => ['driver' => 'smtp', 'from' => MAIL_FROM, 'smtp' => ['host' => SMTP_HOST, 'username' => SMTP_USERNAME, 'password' => SMTP_PASSWORD]],
'alerts'    => ['channels' => ['telegram'], 'telegram' => ['bot_token' => TELEGRAM_BOT_TOKEN, 'chat_id' => TELEGRAM_CHAT_ID]],

The captcha needs both keys: enabled without them, it rejects every form submission unless DEBUG is on. Alerts go only to the channels in app.alerts.channels (alertloop, telegram, mail, log); a listed channel without its settings is skipped. vendor/bin/weft-health lists what is missing.

Documentation

Full documentation lives in example/content/en and is served by the example site itself — read it there: links between chapters resolve on the running site, not on GitHub. Start with Getting Started.

Versioning

Weft follows semantic versioning. Pre-1.0 the API may still change between minor versions.

Security

Please report vulnerabilities privately — see SECURITY.md.

License

MIT © Roman Golovanov. See LICENSE.