Engineering Lok Chan Art

A look behind the engineering of Lok Chan Art — a multilingual production application with payments, secure image uploads, transactional email, and SEO infrastructure.

Published:

Looking for my broader professional engineering experience?

→ Engineering Portfolio

Lok Chan Art may look like a relatively simple art website from the outside. Behind it is a production application that I designed, developed, and maintain independently — from the user interface and internationalization to payments, file storage, transactional email, security, deployment, and SEO.

This section documents some of the engineering decisions behind the project: the problems I encountered, the trade-offs I made, and how the architecture evolved as the application grew.

At a glance

Next.js · React · TypeScript · Stripe · AWS S3 · Resend · Redis · Vercel · Google Workspace

The application includes multilingual content, portrait commission ordering and checkout, secure reference-image uploads, transactional email, an administrative area, rate limiting, SEO infrastructure, and separate development, preview, and production workflows.

Architecture

The application is built with Next.js and TypeScript and deployed on Vercel.

Rather than treating the website as a static portfolio, I designed it as a small production system. A customer can choose a commission format, provide order information, securely upload reference images, complete payment, and receive transactional communication.

The Next.js application acts as the central application layer and integrates with several external services, each with a deliberately separate responsibility.

Lok Chan Art — Production Architecture
High-level architecture of the production application, including payments, secure image storage, webhook-driven order processing, transactional email, and rate limiting.

The main responsibilities are separated as follows:

  • Vercel — application hosting, deployments, custom domains, and environment management
  • Stripe — checkout and payment events
  • AWS S3 — temporary and permanent image storage
  • Resend — transactional email
  • Redis — rate limiting and temporary application state
  • Google Workspace — business email
  • Next.js — application logic, API endpoints, localization, metadata, and rendering

The services are not stages of a single pipeline. The application coordinates them at different points depending on the operation being performed.

A more detailed architecture diagram will be added as the documentation evolves.

Secure image uploads

Portrait commissions require customers to provide reference photographs, so file handling was one of the areas where I deliberately avoided a simple public upload implementation.

Files are uploaded directly to S3 using short-lived authorization rather than passing large files through the application server.

Architecture diagram showing a secure direct-to-storage upload flow with short-lived authorization, temporary S3 storage, checkout, payment webhook, and permanent order storage
Authorization and file transfer follow separate paths: the application issues short-lived authorization, files upload directly to temporary object storage, and paid-order assets are promoted to permanent storage after payment confirmation.

Temporary commission uploads are separated from permanent order assets. Draft files have a limited lifetime, while files belonging to successfully completed orders are retained.

The basic lifecycle is therefore:

short-lived upload authorization → validation → temporary storage → successful checkout → permanent order storage

This prevents abandoned uploads from accumulating indefinitely while preserving assets associated with completed commissions.

The upload flow also includes restrictions on file count and size, request rate limiting, and signed access to non-public assets.

Payments and order processing

Payments are handled through Stripe Checkout.

A successful browser redirect is not treated as proof that an order has been paid. Instead, the application reacts to verified Stripe webhook events.

Text
Customer
   │
   │ starts checkout
   ▼
Stripe Checkout
   │
   ├──────── browser redirect ────────► Customer browser
   │
   │
   └──────── verified webhook ────────► Next.js backend
                                              │
                                              │ finalize order
                                              ▼
                                         Order completed
                                          │          │
                                          ▼          ▼
                                     preserve     send
                                       files      emails
                                          │          │
                                          ▼          ▼
                                        AWS S3     Resend

The browser redirect and the backend payment confirmation therefore have different responsibilities.

The redirect returns the customer to the application, while the verified webhook is what allows the backend to continue the order workflow independently of the browser.

After a successful checkout event:

payment confirmed → order finalized → files preserved → transactional emails sent

This means that closing the browser immediately after payment does not prevent the backend from completing the order.

Transactional email

Transactional email is handled through Resend and is kept separate from the business mailbox managed through Google Workspace.

The application sends different communication to the customer and administrator while keeping credentials and API keys server-side.

Text
                        Lok Chan Art
                             │
                    completed order event
                             │
                             ▼
                           Resend
                         ┌────┴────┐
                         │         │
                         ▼         ▼
                    Customer   Administrator
                      email        email


Google Workspace
      │
      ▼
Business mailbox

Resend and Google Workspace therefore solve separate problems: one is application-generated transactional communication, while the other is the business mailbox used for normal correspondence.

Email authentication for the domain is configured through DNS, including SPF, DKIM, and DMARC.

Internationalization

The site supports multiple languages rather than relying on automatic browser translation.

Localization affects more than visible interface strings. Each locale has its own routes and localized metadata, while canonical URLs and language alternatives help search engines understand the relationship between the different versions.

The content architecture also distinguishes between pages that exist in every supported locale and MDX content that may only have selected translations.

Text
                         Application content
                                │
                 ┌──────────────┴──────────────┐
                 │                             │
                 ▼                             ▼
            Static pages                   MDX content
                 │                             │
        available in all                translation exists?
        supported locales                       │
                 │                     ┌────────┴────────┐
                 │                     │                 │
                 ▼                     ▼                 ▼
          locale alternates       yes: indexable     no: fallback UI
                                    localized page       noindex
                                         │
                                         ▼
                                  actual hreflang
                                    alternatives

This makes internationalization part of both the application architecture and its SEO strategy rather than only a presentation concern.

SEO and performance

SEO infrastructure is generated as part of the application rather than maintained manually for every page.

It includes:

  • localized metadata
  • canonical URLs
  • alternate language URLs
  • sitemap generation
  • robots directives
  • Open Graph metadata
  • structured data

The same content model feeds several SEO mechanisms rather than maintaining independent lists manually.

Text
               Routes + content + locale availability
                              │
          ┌───────────────────┼───────────────────┐
          │                   │                   │
          ▼                   ▼                   ▼
      Metadata             Sitemap            hreflang
          │                   │                   │
          ├──── canonical     │                   │
          ├──── Open Graph    │                   │
          └──── robots        │                   │
                              │                   │
                              └─────────┬─────────┘
                                        ▼
                              Search-engine signals

Performance and accessibility are periodically audited with Lighthouse.

Lighthouse desktop audit generated against the production homepage. Open full report

Read about SEO and performance →

I deliberately avoid adding infrastructure simply because it is common in larger systems. For example, the current image volume does not justify introducing another dedicated CDN layer purely for architectural complexity. The architecture can evolve if traffic or asset volume makes it worthwhile.

Production domain migration

Migrated the application from Sunny Day Art to Lok Chan Art while preserving production availability, payment and email integrations, existing storage, and SEO signals.

The migration used an isolated Preview environment, permanent path-preserving redirects, controlled indexing, and Google Search Console Change of Address.

Text
sunnydayart.com
      │
      │ permanent path-preserving redirect
      ▼
lokchanart.com
      │
      ├──── application configuration
      ├──── Stripe
      ├──── Resend
      ├──── existing S3 storage
      └──── SEO migration signals

Read the case study →

Security decisions

Several parts of the application are intentionally kept server-side or short-lived.

The application uses measures including:

  • server-only application secrets
  • signed authentication tokens
  • short-lived authorization for uploads
  • non-public object storage
  • signed access to protected assets
  • rate limiting
  • restricted S3 CORS configuration
  • Stripe webhook signature verification
  • isolated environment configuration
  • protected Preview deployments

Some of these controls sit at different boundaries of the system rather than forming a single security layer.

Text
Browser
   │
   │ controlled requests
   ▼
Application boundary
   │
   ├──── rate limiting
   ├──── server-only secrets
   ├──── signed / short-lived authorization
   │
   ├──────────────► Stripe
   │                 verified webhook signatures
   │
   └──────────────► S3
                     private objects
                     restricted CORS
                     signed access

Security decisions are kept proportional to the application and its threat model rather than adding complexity without a concrete purpose.

How the architecture can evolve

The current architecture is intentionally proportional to the scale of the application.

If traffic, image volume, or operational requirements grow, some decisions can change.

Text
Current scale
     │
     ├──── simple synchronous workflows
     ├──── current deployment model
     └──── current asset delivery
              │
              │ growth creates a concrete need
              ▼
Possible evolution
     │
     ├──── stronger observability
     ├──── broader automated E2E coverage
     ├──── infrastructure as code
     ├──── asynchronous job processing
     └──── dedicated CDN layer

The goal is not to maximize the number of technologies involved, but to keep the system understandable, maintainable, and appropriate for the problem it solves.