I did not build joacod Lab because the internet needed another portfolio. It already has plenty of those, including some that are mostly screenshots of unfinished dashboards.
I built it because I wanted a place for things that do not fit anywhere else. Interactive experiments, prototypes, small tools, strange ideas, and work that is interesting before it is polished enough to call a product.
At the end of My Personal Blog Is Finally Live, I wrote that I would keep adding functionality, fixing little things, and creating new stuff that sounded cool to build. The Lab is one answer to that. The blog was the place to publish what I had learned. I needed another place to find out what I could learn next.
A blog is not a lab
My blog is mostly static content. That is intentional. I wanted something fast, portable, and easy to own. The Lab has a different job. It needs to host interactive work, state that can survive a session, experiments with different technical requirements, and interfaces that do not all behave the same way.
A screenshot cannot explain how a material feels when you drag across it. A repository cannot give someone the first thirty seconds of a game. A README can document an idea, but it cannot replace opening it and seeing whether the idea is any good.
I also did not want to pretend every experiment was a finished product. Some ideas deserve a public URL before they deserve a launch announcement. The Lab gives them that middle state.
That distinction matters to me. A portfolio usually asks, “What have you completed?” A lab can also ask, “What are you currently trying to understand?”
The Lab is a home, not a platform
The core idea is simple: a shared shell for independent experiments.
The shell handles the boring shared parts: navigation, stable routes, metadata, common application plumbing, and the basic visual language. The experiment owns its own interface. Particle Reactor is a GPU playground. Soft Matter is a material study. Memory Grid is a game with leaderboards. These should feel like they belong to the same place, but they should not be forced into the same component tree or visual template.
That boundary was important from the beginning. As soon as you call something a “Lab”, there is a temptation to build a framework for all possible experiments, with a registry, plugin system, generic data model, and a component for every future scenario. This is how a small project starts looking like a startup that has not found its first user.
I want enough shared structure to make the next experiment easier, but not so much that every experiment becomes an argument with the platform.
The rule is simple: share the infrastructure, not the experiment. The shared layer should make the next experiment easier to build without deciding what every experiment has to be.
The dashboard was the wrong mental model
The project started life as a dashboard. That was a useful first step, but it was the wrong mental model. A dashboard suggests one product, one workflow, and a set of screens that belong to the same system. The Lab is different. Each experiment can have its own interaction model, rendering stack, state, and level of polish.
That change in language made the architecture easier to reason about. The shared part only needs to solve what genuinely repeats: getting around, finding an experiment, keeping routes stable, and providing the common application plumbing. Everything unusual should stay close to the experiment that needs it.
Some experiments are just client-side explorations. Others need persisted state or a more involved backend. Treating them all as the same kind of feature would make the simple ones heavier and the unusual ones harder to change.
This also gave me a better test for what belongs in the core: if something exists because several experiments need it, it may belong in the shared layer. If it exists because one experiment is unusual, it stays local. That sounds obvious, but AI makes premature generalization particularly easy because turning one example into an abstraction is fast.
The stack follows the experiments
The stack is not the most interesting part, but it matters because the wrong foundation makes every experiment harder.
The joacod.com site uses Astro and static rendering, and the Lab uses Next.js and React. That split is not a statement about what either framework can or cannot do. You can build a blog with Next.js, and you can build an interactive site with Astro. I like both, and using both gives me a reason to keep up with their current direction. Since Astro and static rendering were already the foundation of the blog, Next.js was the natural choice for the Lab. React’s ecosystem was another strong reason, especially for the kind of interactive work I wanted to explore.
The current Lab uses:
- Next.js and React for the application shell, routing, metadata, and experiment surfaces.
- TypeScript for explicit contracts and compiler feedback.
- Tailwind CSS, shadcn/ui, and Base UI for styling and accessible primitives without hiding the source behind a large component framework.
- Convex for backend functions, data, and realtime state.
- Better Auth for sign-in and session handling.
- Vercel for hosting the Lab and its deployment workflow.
- Three.js, React Three Fiber, WebGPU, and vgpu where an experiment actually needs graphics.
- Remotion for experiments that benefit from a video composition or a reproducible render.
The important detail is that the heavy or specialized pieces stay close to the experiment that needs them. The shared shell should not pay the cost of a GPU renderer just because one experiment uses it. The Lab is a container for different kinds of work, not an excuse to put every dependency in one global layer.
Convex was the biggest deliberate platform choice in the stack. I could have assembled an API, database, realtime layer, and authentication flow separately. For a small personal project, that would have created a lot of infrastructure before the experiments had earned it. Convex gives the Lab a typed way to work with application data and realtime updates. Better Auth handles sign-in without making every experiment build its own version.
That choice does create coupling. I am not pretending it does not. But coupling is not automatically bad. The question is whether it buys enough simplicity for the project that actually exists. So far, it has.
Hosting follows the same logic. The main site is on Cloudflare and the Lab is on Vercel, but neither choice means the other platform could not host the site. I like working with both, and a personal project is a good place to learn their tools and workflows in practice. This is a preference and a learning decision, not a rule I would apply to every project.
AI-first changes what “good stack” means
I have been building the Lab with AI coding agents in the loop, and that changes how I judge a stack.
I am not asking only whether a technology is powerful or popular. I am asking whether an agent can enter the repository, understand the boundaries, make a focused change, and verify that it did not quietly break something else.
That is a different standard.
An AI agent can generate code for almost any stack. That is not the hard part. The hard part is giving it a repository where the important decisions are visible and the wrong decisions are difficult to hide.
The other reason I like this stack is that the ecosystems are starting to meet agents halfway. Next.js DevTools MCP can connect an agent to a running development server, where it can inspect current build, runtime, and type errors, logs, and route metadata. Convex provides AI helper files and skills, along with its own MCP server. These tools do not replace understanding the code, but they help the agent work from current evidence when something is broken instead of relying only on memory or a copied error message.
This is why I chose a fairly conventional foundation:
- Next.js and React have an explicit application structure and a large amount of current documentation.
- Strict TypeScript turns many plausible mistakes into compiler errors.
- Convex schemas and function contracts make backend assumptions visible.
- Local shadcn/ui components keep the UI implementation inspectable.
- Exact dependency versions reduce “it worked in a different version” surprises.
- Tests cover shared rules and experiment behavior, not just rendered screens.
- Project documentation and agent instructions explain the constraints that code alone cannot.
The AI-first part is not “let the model choose everything”. It is the opposite. It means the repository needs to carry enough context that the agent can work without guessing.
The stack should help the agent reason in small pieces. The shared layer owns the concerns that truly repeat. Each experiment owns its feature UI and any state that only makes sense there. Heavy dependencies stay local. New infrastructure is added when a real experiment needs it, not because the architecture diagram looks empty without it.
Those decisions are useful for human contributors too. That is not a coincidence. A codebase that is legible to an agent is usually a codebase that is legible to its owner.
The boring work taught me the most
I expected to learn about graphics and interfaces. I did, but I learned more from everything surrounding them.
A public page needs good failure states and accessible controls, not only a nice animation. An experiment with stored state needs clear ownership and lifecycle rules. Realtime data is easy to add to a demo and harder to make trustworthy.
The most important lesson is that a small experiment becomes a real product as soon as somebody else needs to use it. You need a stable URL, loading and error states, mobile behavior, persistence with clear rules, deploys, and a way to understand what happens when something goes wrong. None of that is the reason you started, but it determines whether the experiment is usable.
AI makes the first version faster. It does not make these decisions disappear. In fact, it can make them easier to postpone because it can produce a convincing screen before the boring boundaries are settled. This is where AI is both useful and dangerous: it keeps momentum, but it will happily turn an unclear requirement into a complete implementation.
The Lab has made that tradeoff easier to see. The interesting screen is only one part of the work. The rest is deciding what the screen means, what state it owns, how it behaves at the edges, and what should happen when things go wrong.
Why I recommend building a lab
I recommend doing something similar because it creates a short feedback loop between curiosity and evidence.
Reading about a framework, a graphics API, an AI workflow, or a new product idea is useful. Building a small version teaches you a different thing. You have to choose a boundary, live with the choice, see what breaks, and decide whether the problem is the technology or your own assumptions.
A lab also gives unfinished work somewhere to go. Without one, every idea has to become a polished project, a repository with an abandoned README, or a note that you will probably never open again. That is a lot of pressure for a first experiment.
You do not need a custom domain, a database, or the exact stack I chose. A lab can be a single page, a GitHub repository, a folder of prototypes, or a small site. The important part is giving the work a place where it can be used and revisited.
Start with one experiment. Give it a clear name, a small surface, and an honest description of what it is. Let it be incomplete. Add shared infrastructure only when a second experiment proves that the first version was not enough.
The point is not to build a platform for experiments. The point is to make experimenting easier.
A place to keep going
The Lab is still small, and I do not know exactly what it will become. That is part of why I wanted it.
When I created the blog, I wanted a place I owned for writing. The Lab is the same instinct applied to experiments. The blog is where I publish conclusions. The Lab is where I keep the questions alive.
I do not know whether every experiment will become useful. Most probably will not. That is fine. The point is to make the cost of trying small enough that I can keep going.
You do not need a polished product, a large audience, or a perfect stack. You need one idea worth opening, a place to put it, and enough discipline not to build the platform around it first.
A blog is where I write down what I learned. A lab is where I go to find out.