# Connect — Platform and release pipeline for a law firm

> A bilingual site for a Lviv law firm that its team runs itself, and everything under it: Payload inside the Next.js app, Docker on the firm's own Lightsail, Terraform, and a one-command release that audits the exact image it ships and rolls back in seconds.

- Source: https://ivankarpinskyi.dev/work/connect

- Role: Full-stack engineer: design, implementation, CMS, infrastructure, deployment
- Years: 2025 – 2026
- Built by: Alone, design included
- Live: https://connect-law.com.ua/en

- commits: 140
- tests: ~54
- prerendered routes: 45
- Lighthouse mobile, home: 97
- SEO, 9 of 10 pages: 100
- hosting / month: ~$7

Source: the Connect repository, measured during the phase 2 audit (§2).

## § 01 Editable by the firm, never rendered late

The firm edits its own pages in Payload, which runs inside the same Next.js app. Pages are prerendered from the database at build time, so a visitor never waits on the CMS.

## § 02 What runs where

_Fig. 1: Connect: one long-lived process on the firm's own AWS, provisioned by Terraform in two workspaces._

## § 03 Five decisions, written down

1. **One long-lived process, on purpose**: In-process cache invalidation breaks on serverless or across instances.
2. **Migrations before the build**: The build prerenders from the database, so migrations stay additive and a destructive change ships in two releases.
3. **Audit the image that ships**: The checks run against the exact Docker image that goes to the server.
4. **Images over SSH, not a registry**: Lightsail has no instance role, so no IAM keys live on the server.
5. **Fingerprint content around a deploy**: If content changed during the release, the cache is purged.

## § 04 One command to release

_Fig. 2: The release script. Lighthouse runs locally against the build; it is not a pipeline step._

## § 05 Invalidating by tag

Payload hooks invalidate the cache by tag when content changes, so an edit shows on the next request. A daily rebuild is the backstop.

## § 06 Built to be found

Every page is in the sitemap and has a Markdown twin. `llms.txt` and `llms-full.txt` give language models the whole site in one read. OG images are generated per page, and Ukrainian and English each have their own URLs. SEO scores 100 on 9 of 10 pages on mobile.

> We needed a site that looks as serious as our practice. Ivan handled the design, the build and the launch, and the team updates the content without calling anyone. Communication was clear the whole way through.
>
> — Managing Partner, Connect

## Stack

Next.js · React · TypeScript · Payload CMS · Tailwind CSS · SQLite · Turso · Docker · AWS Lightsail · Caddy · Terraform · S3 + CloudFront · Resend

## Related notes

- [Serverless Postgres: where scale-to-zero actually reaches zero](https://ivankarpinskyi.dev/blog/serverless-postgres-vs-instance.md) (2026-08-28, Decision): Scale-to-zero rarely reaches zero. How to compute the serverless-versus-provisioned Postgres crossover from your workload's duty cycle.
- [Your query's latency lives where you deployed it](https://ivankarpinskyi.dev/blog/query-path-await-to-response.md) (2026-09-20, Mechanism): The path from await query() to a row has five measurable stages. Which one owns your latency is decided by deployment shape, not by the query.
