2026-10-25T14:09:25.098Z
One design language, two workflows: my portfolio and TechCanvas on AWS

I wanted my portfolio to do more than list projects. It should connect the systems I build, the ideas I learn, and the explanations I share. TechCanvas adds the visual learning side of that workflow: take a technical concept, turn it into a content pack, and bring the result into an engineering notebook.
The result is a portfolio and writing platform at https://lokeshburma.com, with TechCanvas linked into the same visual experience. The two applications share a dark canvas and lavender accents, while keeping their backend and authentication boundaries explicit.
This post explains the application as it is deployed today, including the parts that are deliberately switched off.
The design in one view
The architecture illustration follows two journeys. Visitors browse the public portfolio and published writing. The owner creates or imports content, saves it as a private draft, reviews it, and explicitly chooses when to publish.
The live portfolio path is:
Browser → lokeshburma.com → CloudFront
↓ signed origin request
Next.js portfolio
↓ server-side API bridge
NestJS API
↓
DynamoDBTechCanvas is a separate studio. Its content-pack export joins the portfolio through a manual draft import. It is not a shared-login system or an automatic publishing pipeline.
A coherent frontend, with clear responsibilities
Next.js serves the portfolio: home, projects, writing, experience, about, contact, and the TechCanvas introduction. I carried the studio's visual language across these pages so moving between a project, an explanation, and a learning tool feels consistent.
The visual design uses a near-black background, raised panels, restrained lavender highlights, readable muted text, and visible keyboard focus. Navigation and layouts work on desktop and narrow mobile screens.
Behind the interface, the standalone NestJS API owns blog operations and portfolio owner authentication. A server-side bridge lets the browser use same-origin routes such as /api/admin/blogs. The backend address and owner credentials are not exposed as public frontend environment variables.
This split keeps the frontend focused on presentation while giving the API a clear responsibility for validation, authentication, and storage.
From a custom domain to the application
Vercel still manages DNS for lokeshburma.com. It is no longer the hosting path for the website. The apex points to CloudFront, and www redirects to the canonical apex while preserving the path and query string.
CloudFront terminates public HTTPS. Its certificate covers both hostnames and lives in us-east-1, as CloudFront requires. The application servers, database and logs run on AWS.
The portfolio and API run as Node.js 24 Lambda Web Functions. They are ordinary HTTP servers packaged for Linux ARM64, rather than applications running on a continuously managed virtual machine.
The production portfolio endpoint requires IAM authentication. CloudFront signs its origin requests, and a resource policy limits invocation of that endpoint to the intended distribution. Direct unsigned requests to that protected endpoint are rejected.
CloudFront caches hashed Next.js static assets. Its default behavior disables caching for application routes, and error caching is disabled. Owner and API responses must not become shared cached content. Security response headers add a content security policy, HSTS, frame protection and MIME sniffing protection.
Writing storage that keeps drafts private
DynamoDB stores blog posts and their supporting lookup records. The adapter uses conditional transactions to keep post creation, slug ownership and updates consistent. A conflicting slug cannot silently replace another post.
Public reads do more than find an entry in a list. They load the current post and check its current publication status. That matters when a post changes from published back to draft: stale list information must not expose private content.
The owner uses a signed, host-only session cookie with Secure, HttpOnly and SameSite protections. Password verification uses PBKDF2. Writes also check the request origin. Authentication and origin checks have different jobs; an origin check alone does not establish who the owner is.
TechCanvas has its own authentication boundary: Amazon Cognito, MFA and authenticated API routes. I have not joined the two applications with single sign-on.
Where TechCanvas joins the workflow
TechCanvas organizes a concept into reusable learning material. Its diagram rendering is code-based; the intended text-model role is to help produce structured explanations, not to act as a raster image generator.
A content pack can include a portfolio.json export. The portfolio editor imports that file as an unsaved draft. It derives the draft slug, ignores an existing post identity, forces draft status and clears the imported cover URL rather than trusting a potentially expiring private link.
Importing does not save or publish anything automatically. The owner can edit the title, summary, tags and markdown before saving. Publication is a later, explicit action.
The studio UI is a separate static frontend, served from private S3 through its own CloudFront distribution. It calls an HTTP API backed by Lambda, with Cognito handling the studio authentication boundary. This is distinct from the Next.js and NestJS portfolio path.
The cloud foundation behind the studio includes a private S3 asset store, a DynamoDB jobs/quota/outbox table, a dispatcher, an SQS FIFO generation queue, a worker, and dead-letter queues. Those are infrastructure and implementation pieces, not a claim that every workflow is active.
Today, paid Bedrock inference and both processing triggers remain disabled. The worker has no Bedrock invocation permission. Turning on hosted generation will need a separate review of model access, quality, limits and cost. This architecture article does not change that setting.
A small budget needs operational controls
My target is to keep this personal platform around ₹1,000 per month. That is a planning target, not a hard billing cap or a promise of free hosting.
The design avoids an always-on relational database, NAT gateway and virtual-machine fleet. It uses on-demand storage, cached immutable assets, short log retention and endpoint scaling limits. The runtime roles receive scoped access to the resources they need.
There is an important tradeoff with Web Functions: memory is billed over the lifetime of an execution environment, including time between requests. Request count alone is not enough to estimate the bill. A short validation run observed memory peaks around 195 MB for the portfolio and 221 MB for the API, but that sample cannot establish a monthly charge.
The next useful measurements are environment lifetime, actual memory consumption, traffic, CPU, log volume and retained storage. Scaling limits reduce exposure; they do not guarantee a maximum bill. Paid inference remains off while those operating assumptions are reviewed.
What deployment and testing taught me
A successful build on my Mac was not sufficient evidence that the deployed package would work. I built both applications for Linux ARM64 and tested their actual startup entrypoints inside Docker using Colima.
One packaging issue was especially useful: flattening Next.js's internal pnpm dependency links broke module resolution. Preserving those relative links, and checking that they stay inside the artifact, produced a working standalone package. I also pinned a TypeScript compiler version compatible with the Next.js build.
Infrastructure checks cover regional boundaries, private retained storage, owner-secret placement, signed origin access, cache behavior and canonical redirects. Before changing public DNS, I tested the custom hostname against CloudFront with a local DNS override. After cutover, I repeated the browser workflow using normal public DNS.
The live checks exercised responsive pages, owner login and logout, secure cookies, TechCanvas import, a real DynamoDB draft save, public draft isolation and cleanup of the temporary record. Anonymous admin reads returned 401, cross-site writes returned 403, and repeated requests for a hashed JavaScript asset produced a CloudFront cache hit.
What comes next
The immediate workflow is intentionally straightforward: create, import as draft, review, then publish manually. The old sample blog posts were not migrated.
Next, I want to refine the writing and visual explanations, measure the platform over a representative period, and improve media handling for reviewed content. Hosted model inference can be evaluated later with explicit spending and quality controls.
The useful outcome is already in place: a portfolio, an engineering notebook and a visual learning studio that feel connected, with clear boundaries around authentication, publishing and cost.
References
- AWS Lambda Web Functions: https://docs.aws.amazon.com/lambda/latest/dg/web-functions.html
- CloudFront protection for Web Function origins: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-lambda-web.html
- CloudFront certificate requirements: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-requirements.html
- Vercel DNS records: https://vercel.com/docs/domains/working-with-dns