I've been stuck in what I call "boilerplate purgatory" for the last six months on our internal component library. Every new React component required the same tedious ritual: create the `.tsx` file, the `.module.scss` file, the `index.ts` barrel export, the `Component.stories.tsx`, and the `Component.test.tsx`. The mental context switch from designing the actual logic to setting up these five empty files was murder on flow state.
Enter Aider. I'd been using it for pair-programming on bug fixes, but its `--template` flag caught my eye. I decided to see if I could weaponize it against the boilerplate problem. The goal was a single command that would generate the entire, interconnected component skeleton from a one-line description.
The setup wasn't trivial, but the payoff was. I created a template directory with Mustache templates for each file type. The key was the `aider.toml` configuration to map a specific prompt pattern to the template set.
Here's the core of the `aider.toml`:
```toml
[template_dirs]
components = "./.aider/templates/components"
[templates.components]
description = "Generate a new React component with stories and tests"
prompt = "Create a new component called {name} that {description}"
files = [
"src/components/{{name}}/{{pascalCase name}}.tsx",
"src/components/{{name}}/{{pascalCase name}}.module.scss",
"src/components/{{name}}/index.ts",
"src/components/{{name}}/{{pascalCase name}}.stories.tsx",
"src/components/{{name}}/{{pascalCase name}}.test.tsx"
]
```
Each template file contains the standard structure with Mustache placeholders. The `.stories.tsx` template imports from the yet-to-exist component file, the `index.ts` re-exports it, etc.
Now, instead of the 15-minute file creation and import dance, I run:
`aider --template components "Create a new component called StatusBadge that displays a colored badge based on an enum prop"`
It generates all five files with correct import paths, a basic prop interface, a placeholder SCSS class, a default Storybook story, and a vitest test skeleton. The generated component is, of course, just a shell—but it's a *correct* shell, with all the cross-references already wired. This eliminates the "module not found" errors that always sneak in when I do this manually at 4 PM on a Friday.
The real win is consistency. Every component now starts with the same linting rules, the same comment structure for props, the same story args pattern. It's like enforcing an API contract for your own component creation process.
I've since expanded this to two other boilerplate-heavy areas:
* **API Service Modules:** Templates for the Axios instance wrapper, request/response type definitions, and a query hook for TanStack Query.
* **Custom Middleware Handlers:** For our Express-based integration layer, generating the skeleton for a new webhook endpoint with validation, logging, and error handling stubs.
The middleware template alone saved me from writing the same `try-catch` block and Slack alert function for the tenth time. It’s not that I couldn't write it; it's that I *shouldn't have to*. Let the machine handle the predictable parts.
Has anyone else used Aider's templating system to automate their own repetitive patterns? I'm particularly curious if you've found clever ways to chain templates or inject context from existing project files that my setup might be missing.
APIs are not magic.
Ooh, I love seeing Aider used this way! The `--template` flag is a game-changer for IaC too. I've been using a similar setup to generate Terraform module scaffolds, but you've given me an idea to bake in some security guardrails from the start.
Could you share a snippet of one of your Mustache templates? I'm curious how you handle the variable substitutions for the component name across all those different file types. I bet the barrel export template is just a one-liner, but the `.stories.tsx` one must have some logic.
Infrastructure as code is the only way
> bake in some security guardrails from the start
That's a great idea. I've been wondering about how to prevent certain patterns from getting templated in the first place. With Terraform, are you embedding something like a required provider block, or are the guardrails more about tagging and naming conventions?
On the variable substitution, the Mustache tags are the same everywhere, it's just the filenames in the Aider config that change. The real trick for the .stories.tsx file was figuring out the default control types for our props. How are you managing those variable definitions across different Terraform resource types?
Good question on the guardrail scope. We're enforcing both structural and security rules.
For naming/tagging, the template forces a naming convention prefix. Any resource name variable must match `{{ prefix }}_{{ resource_type }}_{{ name }}`. The template won't render if a user's input is missing the required parts.
For required provider blocks and version pins, we use a different approach: a centralized configuration file that the generated module imports. The template itself just has the import statement. This ensures updates happen in one place, not across hundreds of template files.
For props and controls, our Terraform analogue is variable validation blocks. The template generates a basic `validation {}` block for any variable we've flagged as requiring a specific pattern, like subnet CIDR formats. More complex logic is still manual, but the scaffold is there.
How are you defining default controls in your React setup? Is it based on prop type (string, boolean) or something more semantic?
Data is the only truth.
>centralized configuration file that the generated module imports
That's the right call. It prevents the usual drift where half the generated modules reference `~> 4.0` and the other half get `"= 4.56.1"` because someone copy-pasted from a blog post.
On default controls, we mapped them purely by TypeScript type at first (boolean -> checkbox). It was predictably naive. A `variant` prop typed as `'primary' | 'secondary'` isn't a string input; it's a radio group. We've shifted to a small lookup table mapping specific prop *names* to specific Storybook controls. It's a hack, but less brittle than trying to divine semantics from types alone.
Where's your single source of truth for which variables get those validation blocks? Another config file, or is it embedded in the template logic itself?
- Nina
The validation block source of truth is a YAML config that gets parsed by the template engine. It maps variable names to regex patterns or allowed lists.
Your lookup table approach for Storybook controls is the pragmatic choice. We did something similar for Terraform variable `description` fields, where certain names trigger specific boilerplate text about security or cost. Trying to infer intent from a variable name like `cidr_blocks` is easier than trying to parse its type definition.
The problem with baking it into the template logic is maintenance. Now you're editing templates to change business rules, which defeats half the purpose.
Your fancy demo doesn't scale.
Centralized config's a good move, but you're shifting the drift risk. Now you've got drift between the config file and the templates that consume it. Who updates the config when a new resource type is added? I've seen teams generate broken modules for a week because the template expected a new field in the YAML that didn't exist.
That validation on `cidr_blocks` is table stakes. The real cost is when your template *doesn't* generate a validation for something like `instance_type`. Let it through as a free-form string and watch your bill when someone deploys a hundred `p4d.24xlarge` instances by accident. The template should enforce a dropdown of approved, cost-optimized types.
show the math
Drift in the config file is the new boilerplate problem, just shifted one layer up. Someone's still stuck maintaining a YAML map instead of writing modules.
>the template should enforce a dropdown of approved, cost-optimized types.
And now you're back to hardcoding business logic. The team in finance who sets the approved list isn't editing your YAML config. So you either generate outdated modules or become the bottleneck for every infrastructure request. Pick your poison.
CRM is a necessary evil
Oh good, another layer of abstraction to maintain. Your `aider.toml` is a project config file that's now a dependency for spinning up a simple component. What happens when the new dev clones the repo and their Aider install doesn't pick up the local config? They'll generate garbage until they stumble on the magic file.
You traded writing five files for writing and debugging a dozen Mustache templates. That's not automation, that's a transfer of effort. The "flow state" you saved gets spent later untangling why the story template broke after a Storybook version bump.
The payoff only exists if you ignore the setup and maintenance time. Classic.
If it ain't broke, don't 'upgrade' it.
The config file problem is real, but that's a tooling problem, not a template problem. Aider should read the local aider.toml by default.
You're right about maintenance cost, but you're missing the scale. I debug one template once, then generate 200 identical components. That's the payoff. If your component specs are stable, the setup time amortizes to zero. If they change every week, then yeah, you're just building a maintenance trap.
—cp
The security guardrail question is interesting, but it glosses over the root problem. "Baking in" guardrails presumes your template is the only generation path.
If the Mustache tags are the same, what's stopping a developer from bypassing Aider entirely and running a raw mustache CLI with a custom data file? They will, the moment the template blocks a "quick experiment." Your guardrails are only as strong as your team's willingness to use the official toolchain, which is usually nil.
Question everything
Great point about the config drift. We got bitten by that when our team added a new `backend_service` type but forgot to add the required `protocol` field mapping. CI didn't catch it because the template just renders an empty string for missing keys.
The finance-approved `instance_type` list is the perfect example. That *shouldn't* be in our template's YAML config, you're right. We've started pulling that from a separate API call during generation. It pushes the ownership problem to the API layer, but at least the source of truth is owned by the right team.
But it adds a new failure mode: generation now depends on network calls.
Clean code is not an option, it's a sanity measure.
>mental context switch from designing the actual logic to setting up these five empty files was murder on flow state.
That's exactly what I struggle with. I'm curious, once you set up the `aider.toml` and templates, how much time does it actually take to run the command for a new component now? Is it like a few seconds?
Yeah, a few seconds after the initial investment. The command is just `aider generate component Button` and it spits out the five files ready to go.
But you've got a point - the flow state isn't just about raw seconds. It's about not having to remember the exact folder structure, naming conventions, or which imports are standard for a `Table` vs a `Card`. That mental tax was the real killer for me.
Of course, this only works if your component patterns are pretty settled. If you're still experimenting with architectures every other week, the template maintenance will eat any time you saved.
That's exactly the mental tax I'm trying to avoid. Forgetting which subfolder the Storybook file goes in is such a tiny thing, but it completely derails me.
Do you ever worry that automating the conventions like this means newer team members won't actually learn the underlying structure? Or is that an okay trade-off for speed?