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 PortfolioLok 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.

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.
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.
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.
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.
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.
Routes + content + locale availability
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
Metadata Sitemap hreflang
│ │ │
├──── canonical │ │
├──── Open Graph │ │
└──── robots │ │
│ │
└─────────┬─────────┘
▼
Search-engine signals
Performance and accessibility are periodically audited with Lighthouse.
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.
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.
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.
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.