I get asked about our tech stack a lot. Usually by developers who have seen our site, noticed it is fast, and want to know what is under the hood. Sometimes by clients who want to understand how we build things. Occasionally by other agency founders who are evaluating their own choices.
So here is the complete breakdown. Every tool, every library, every service we use to run codercops.com and the CODERCOPS agency infrastructure -- and the reasoning behind each choice. I am going to be specific because vague "we use modern tools" answers help nobody.
Every tool earns its place. If it does not solve a specific problem better than the alternatives, it gets replaced.
The Architecture Overview
Before I go tool-by-tool, here is the high-level view of how everything fits together.
CODERCOPS Agency Architecture (2026)
+------------------------------------------+
| codercops-agency |
| (GitHub Org) |
+------------------------------------------+
| |
v v
+----------------+ +----------------------+
| agency-website | | agency-content |
| (Astro 5.x) | | (MDX + Markdown) |
| | | |
| - Pages/Routes | | - Blog posts (.mdx) |
| - Components | | - Projects (.md) |
| - API Routes | | - Images |
| - OG Generator | | - GitHub Actions CI |
| - Contact Form | +----------+-----------+
+-------+--------+ |
| |
| Git submodule |
+<---------------------+
|
v
+----------------+ +------------------+
| Vercel | | Airtable |
| (Deployment) | | (CRM) |
| - Auto deploys | | - Leads |
| - Edge funcs | | - Projects |
| - Analytics | | - Contacts |
+----------------+ +------------------+
|
v
+----------------+ +------------------+
| Nodemailer | | Unsplash |
| (SMTP Email) | | (Blog images) |
| - Contact form | | - Hero images |
| - Notifications| | - Inline images |
+----------------+ +------------------+Two repositories. One for the website code. One for the content. Connected via Git submodule. Deployed on Vercel. Let me explain why.
The Framework: Astro 5.x
Why Astro, Not Next.js
This is the question I get most often. Next.js is the default choice for agency websites in 2026. Why did we choose Astro?
The answer is simple: codercops.com is primarily a content site. It has a blog, project case studies, service pages, and a contact form. It does not have user authentication, a dashboard, real-time data, or complex client-side state. For this type of site, Astro's architecture is objectively better.
| Criterion | Next.js | Astro | Winner for Our Use Case |
|---|---|---|---|
| JavaScript shipped to client | Heavy (React runtime) | Zero by default (islands) | Astro |
| Content collections | Possible but not native | First-class MDX/MD support | Astro |
| Build performance | Good | Faster for content sites | Astro |
| Learning curve for content | Steeper | Shallow (it is just HTML + MD) | Astro |
| Deployment flexibility | Vercel-optimized | Any static host + adapters | Astro |
| Client-side interactivity | Excellent | Good (via islands) | Next.js |
| API routes | Excellent | Good (via adapters) | Next.js |
| Ecosystem/plugins | Massive | Growing, sufficient | Next.js |
For an agency site that is 90% static content and 10% interactivity (contact form, theme toggle, mobile menu), Astro ships dramatically less JavaScript. Our homepage loads with near-zero client-side JS. That translates to faster load times, better Core Web Vitals, and better SEO.
To be clear: we use Next.js for client projects all the time. The Venting Spot, Colleatz, Lore Web3, AI Interview, Parivartan Samiti, Sarmistha Cloud Kitchen -- all built with Next.js. It is excellent for applications with complex interactivity. But for our own agency site, Astro is the right tool.
Astro 5.x Specifics
We are on Astro 5.x, which gives us:
- Content Collections with type safety -- our blog posts and project case studies are validated at build time. If a post is missing a required frontmatter field, the build fails. This catches errors before deployment.
- MDX support via @astrojs/mdx -- blog posts are written in MDX, which gives us Markdown simplicity with the ability to embed interactive components when needed.
- Sitemap generation via @astrojs/sitemap -- automatic sitemap generation for SEO.
- View Transitions -- smooth page transitions without a client-side router.
The Content System: Separate Repository + Git Submodule
This is the architectural decision I am most proud of, and the one other agencies find most unusual.
The Problem
Content and code have different change frequencies and different contributors. Blog posts are written and published multiple times per week. The website code changes maybe once or twice a month. Mixing them in one repository creates noise -- content commits clutter the code history, code deployments risk breaking content, and the CI pipeline runs unnecessary checks on every blog post.
The Solution
Two repositories:
- codercops-agency-website -- the Astro application. All code, components, styles, API routes.
- codercops-agency-content -- all content. Blog posts (MDX), project case studies (Markdown), and images.
The content repo is included in the website repo as a Git submodule. The website reads content from the submodule at build time.
codercops-agency-website/
src/
components/
layouts/
pages/
styles/
utils/
codercops-agency-content/ <-- Git submodule
blog/
why-we-chose-ai-first-agency-2026.mdx
running-tech-studio-india-global-clients-2026.mdx
...
projects/
the-venting-spot.md
querylytic.md
...
images/
blog/
projects/
package.json
astro.config.mjsWhy This Works
Content independence. I can publish a blog post by committing to the content repo without touching the website code. The content repo has its own CI (GitHub Actions) that validates frontmatter, checks for broken image references, and ensures content quality.
Cleaner git history. The website repo's git log shows code changes. The content repo's git log shows content changes. No mixing.
Separate permissions. Contributors who write blog posts do not need access to the website codebase. This matters as the team grows.
Faster builds. When only content changes, we can trigger a targeted rebuild. When only code changes, we do not need to re-process all content.
GitHub Actions CI for Content
The content repo runs automated checks on every push:
- Frontmatter validation -- every blog post must have title, description, pubDate, author, image, tags, category, and subcategory
- Image reference checks -- if a post references an image path, the file must exist
- Link validation -- external URLs are checked for 404s (non-blocking, but flagged)
- File naming convention -- slug format enforced (lowercase, hyphens, year suffix)
This catches most content errors before they make it to the website build.
Deployment: Vercel
We use Vercel via the @astrojs/vercel adapter. The decision here was straightforward:
- Git-based deployments -- push to main, site deploys. No build scripts to maintain.
- Preview deployments -- every PR gets a preview URL. We review content changes visually before merging.
- Edge functions -- our API routes (contact form, OG image generation) run on Vercel's edge network.
- Analytics -- built-in Web Vitals tracking without additional tooling.
- Custom domains -- SSL, DNS management, zero configuration.
We considered Cloudflare Pages (faster edge, cheaper at scale), Netlify (good Astro support), and self-hosting on a VPS (maximum control). Vercel won because the Astro integration is mature, the developer experience is excellent, and the free tier is generous enough for an agency site.
Cost Reality
For our current traffic levels, Vercel's free tier covers everything. The Pro plan ($20/month) is there if we need it. Total hosting cost for codercops.com: $0-20/month. That is the kind of number that makes self-hosting hard to justify -- the time spent maintaining a server costs more than $20/month of developer time.
Styling: CSS Custom Properties (No Tailwind)
Yes, you read that correctly. We do not use Tailwind CSS on our own site.
Before the pitchforks come out: we use Tailwind on many client projects. The Venting Spot, Colleatz, Parivartan Samiti, Sarmistha Cloud Kitchen, Luxury Lodgings -- all Tailwind. It is a good tool for rapid development, especially on larger teams where design consistency is a challenge.
But for our own site, we chose vanilla CSS with custom properties. Here is why:
The Reasoning
Design system control. Our site has a specific design language -- a dark/light theme, particular spacing scales, custom animations. CSS custom properties give us complete control over the design tokens without the abstraction layer of a utility framework.
Smaller CSS bundle. Our total CSS is approximately 15-20KB. A Tailwind build, even with purging, would be similar in size but with less semantic meaning in the HTML.
Readability. When I open a component, I see
class="hero-section"notclass="flex flex-col items-center justify-center min-h-screen bg-gradient-to-b from-gray-900 to-gray-800 px-4 sm:px-6 lg:px-8". For a small team maintaining its own site, readability wins.Theme implementation. Our dark/light theme toggle works via CSS custom properties on the
:rootelement. Switching themes means changing a handful of custom property values. No conditional class logic, no theme provider components.
/* Simplified example of our theming approach */
:root {
--color-bg-primary: #ffffff;
--color-text-primary: #1a1a1a;
--color-accent: #3b82f6;
--spacing-base: 1rem;
--radius-default: 0.5rem;
}
[data-theme="dark"] {
--color-bg-primary: #0f0f0f;
--color-text-primary: #e5e5e5;
--color-accent: #60a5fa;
}
.hero-section {
background: var(--color-bg-primary);
color: var(--color-text-primary);
padding: calc(var(--spacing-base) * 4);
}The Tradeoff
The tradeoff is that we maintain our own design system instead of using Tailwind's. For a team of our size, this is manageable. For a larger team, Tailwind's enforced consistency would probably win.
Dynamic OG Images: Satori + resvg-wasm
Every blog post and project page on codercops.com has a custom Open Graph image generated dynamically. When you share a post on Twitter, LinkedIn, or Slack, the preview image includes the post title, description, author name, and CODERCOPS branding. These are not manually created in Photoshop. They are generated at build time using code.
How It Works
Blog Post Frontmatter
|
v
Satori (JSX -> SVG)
|
v
resvg-wasm (SVG -> PNG)
|
v
/og/post-slug.pngSatori is a library by Vercel that converts JSX-like markup into SVG. You write what looks like a React component describing the layout, and Satori produces an SVG string.
resvg-wasm converts that SVG into a PNG image. It runs as WebAssembly, which means it works in serverless/edge environments without native dependencies.
The result: every page has a unique, branded OG image without manual design work. When we publish 4 blog posts in a week, that is 4 custom OG images generated automatically. Over 120+ blog posts, this saves dozens of hours.
Why Not a Service Like Cloudinary or OG.ninja?
We evaluated third-party OG image services. They work well but add:
- External dependency (if their service is down, our OG images break)
- Per-image costs at scale
- Less design control
Satori + resvg-wasm is self-contained, runs at build time, and gives us pixel-perfect control over the output. The initial setup took about a day. Ongoing maintenance is zero.
Syntax Highlighting: Shiki
Our blog posts contain a lot of code examples. We use Shiki (which is built into Astro's MDX pipeline) for syntax highlighting. Shiki uses TextMate grammars -- the same grammars that VS Code uses -- which means the syntax highlighting on our blog matches what developers see in their editor.
Why not highlight.js or Prism? Shiki produces better output for edge cases (JSX inside Markdown, nested code blocks, language-specific tokens), and it runs at build time so there is zero client-side JavaScript for syntax highlighting. The HTML ships with inline styles on each token. No client-side parsing.
We also have highlight.js in our dependencies for specific use cases where we need runtime highlighting (like dynamically rendered content), but the vast majority of syntax highlighting is handled by Shiki at build time.
Contact Form: Nodemailer + SMTP
Our contact form submits to an Astro API route that uses Nodemailer to send emails via SMTP. No third-party form service. No Formspree. No Typeform.
Why Self-Hosted Email
- Full control over the email template. We format contact submissions exactly how we want them -- including IP address, user agent, referral source, and timestamp.
- No per-submission costs. Form services charge per submission after a free tier. Nodemailer + SMTP is effectively free.
- Data stays on our infrastructure. Contact form submissions are not stored on a third-party service.
- Integration with our workflow. The email goes directly to our inbox and triggers an Airtable automation that creates a CRM entry.
The setup is simple: an API route receives the form data, validates it (server-side, never trust client-side validation), and sends an email via Nodemailer with our SMTP credentials.
User submits form
|
v
Astro API Route (/api/contact)
|
v
Server-side validation
|
v
Nodemailer -> SMTP -> Email to inbox
|
v
Airtable webhook -> CRM entryCRM: Airtable
We use Airtable as our CRM. Not Salesforce. Not HubSpot. Not a purpose-built CRM. Airtable.
Why Airtable
For an agency of our size, a full CRM is overkill. We do not need lead scoring algorithms, automated email sequences, or sales pipeline visualization with 15 stages. We need:
- A list of leads with contact info and status
- A list of active projects with timelines and notes
- A list of past clients for follow-up
Airtable does all of this with a spreadsheet-like interface that everyone on the team can use without training. The API is simple, the automations are sufficient (trigger an email when a lead status changes), and the cost is reasonable.
| CRM Option | Monthly Cost | Complexity | Our Assessment |
|---|---|---|---|
| Salesforce | $75+/user | Very high | Massive overkill for a small agency |
| HubSpot | $0-50/user | Medium | Good but more features than we need |
| Pipedrive | $15/user | Medium-low | Decent but another tool to learn |
| Airtable | $0-20/user | Low | Right level of complexity for us |
| Spreadsheet | $0 | Very low | No automations, no API, eventually breaks |
The Airtable Structure
We have three main tables:
- Leads -- Name, email, company, source, status (New / Contacted / Qualified / Proposal Sent / Won / Lost), notes, follow-up date.
- Projects -- Client, project name, status, start date, end date, budget, tech stack, team members.
- Contacts -- Long-term contact database for newsletters, updates, and relationship management.
When someone fills out our contact form, the API route sends an email to our inbox AND creates a new entry in the Leads table. This automation takes about 5 minutes to set up and saves us from manually entering every lead.
Testing: Vitest + Puppeteer
We test our own site. This is surprisingly rare among agencies -- most treat their own website as a side project that does not warrant the same rigor as client work. We disagree.
Vitest for Unit Tests
We use Vitest (the Vite-native test runner) for unit and integration tests:
- Content validation -- tests that verify all blog posts have required frontmatter fields
- Utility functions -- date formatting, slug generation, reading time calculation
- OG image generation -- tests that verify Satori produces valid SVG output
- API route logic -- contact form validation, email formatting
Puppeteer for Visual/E2E Tests
Puppeteer handles browser-based tests:
- Screenshot capture -- automated screenshots of key pages for visual regression
- Form submission -- end-to-end test of the contact form
- Theme toggle -- verify dark/light mode works correctly
- Navigation -- verify all links resolve and pages render
Why We Test Our Own Site
Two reasons:
- Credibility. If we tell clients that testing is important (and we do), our own site should be tested. Practice what you preach.
- Content deploys are frequent. We publish 3-5 blog posts per week. Each publish triggers a build. Automated tests catch broken builds before they hit production.
The Full Tool List
Here is every tool and service in our stack, for those who want the complete picture:
| Category | Tool | Purpose | Cost |
|---|---|---|---|
| Framework | Astro 5.x | Static site generation with MDX | Free (OSS) |
| MDX Integration | @astrojs/mdx | Blog post rendering | Free (OSS) |
| Sitemap | @astrojs/sitemap | Auto-generated sitemap | Free (OSS) |
| Deployment | Vercel + @astrojs/vercel | Hosting, edge functions, CI/CD | Free tier |
| OG Images | Satori + satori-html | JSX to SVG conversion | Free (OSS) |
| OG Images | @resvg/resvg-wasm | SVG to PNG conversion | Free (OSS) |
| Syntax Highlighting | Shiki (built into Astro) | Code block rendering | Free (OSS) |
| Syntax Highlighting | highlight.js | Runtime code highlighting | Free (OSS) |
| Nodemailer | SMTP email for contact form | Free (OSS) | |
| Markdown Processing | marked | Markdown to HTML for project pages | Free (OSS) |
| YAML Parsing | yaml | Frontmatter parsing | Free (OSS) |
| CRM | Airtable | Lead and project management | Free-$20/mo |
| Content Repo | GitHub + Git submodules | Content management + versioning | Free |
| Content CI | GitHub Actions | Automated content validation | Free tier |
| Testing | Vitest + @vitest/ui | Unit and integration tests | Free (OSS) |
| E2E Testing | Puppeteer | Browser-based testing | Free (OSS) |
| CSS | Custom properties (vanilla CSS) | Styling with dark/light theme | N/A |
| Fonts | System font stack | Typography | Free |
| Images | Unsplash (blog) + Custom (projects) | Visual content | Free |
| Domain | Namecheap | Domain registration | ~$12/year |
| Version Control | GitHub | Code and content management | Free |
Total monthly cost for running codercops.com: approximately $0-20.
Decisions We Considered and Rejected
Tailwind CSS
Already covered above. Good tool, wrong fit for our specific site. We use it extensively on client projects.
Contentful / Sanity / Strapi (Headless CMS)
A headless CMS would add a visual content editing interface. We considered this and rejected it because:
- Our content is written in VS Code, not a CMS editor
- MDX gives us more flexibility than any CMS WYSIWYG
- A headless CMS is another service to maintain, pay for, and authenticate against
- Git-based content (our current approach) gives us version history, branching, and PR reviews for free
If we had non-technical content contributors, a CMS would make sense. We do not, so it does not.
WordPress
No. Life is too short.
Database (Postgres, MongoDB, etc.)
Our site does not have user accounts, dynamic data, or anything that requires a persistent database. Everything is static, generated at build time from MDX and Markdown files. Adding a database would add complexity, hosting costs, and a failure point for zero benefit.
The Airtable CRM is the closest thing we have to a database, and it is accessed via API only -- not directly from the website.
Redis / Caching Layer
Not needed. Astro generates static HTML. Vercel's CDN handles caching. There is nothing to cache in our application layer.
Analytics (Google Analytics, Plausible, etc.)
We use Vercel Analytics, which is built into our deployment platform. We considered adding Plausible (privacy-friendly) or Google Analytics (comprehensive), but Vercel Analytics gives us what we need -- page views, Web Vitals, geographic distribution -- without an additional script tag and GDPR compliance burden.
Lessons from Our Own Stack
1. Boring Technology Wins
Our stack is not exciting. Astro, Vercel, vanilla CSS, Nodemailer. No cutting-edge framework. No bleeding-edge deployment platform. No experimental CSS-in-JS library.
But it works. It is fast. It is cheap. It is easy to maintain. When something breaks (rare), I can debug it in minutes, not hours. The most boring stack decision that actually works is better than the most exciting stack decision that requires constant maintenance.
2. Separate Concerns Early
The two-repo architecture (code + content) was a decision we made early and have never regretted. It adds a small amount of complexity (Git submodules have their quirks), but the benefits in terms of clean separation, independent deployment, and focused git history are worth it.
3. Generate, Do Not Design, Repetitive Assets
The dynamic OG image generation was an investment that paid off immediately. 120+ blog posts, each with a custom OG image, generated automatically. The alternative -- designing each one in Figma -- would have taken hours per week.
4. Test Your Own Site
The number of agencies whose own websites have broken links, missing images, or 404 pages is embarrassing. Automated tests catch these issues. It takes a day to set up and runs in seconds on every deploy.
5. Minimize External Dependencies
Every external service is a potential failure point and a potential cost center. We use three external services (Vercel, Airtable, SMTP provider). Everything else is self-contained in the build. This is a deliberate choice.
What Might Change
No tech stack is permanent. Here is what we are watching:
- Astro 6.x -- the beta looks promising, especially the improved edge computing support. We will upgrade when it stabilizes.
- Cloudflare -- if Vercel's pricing changes or Cloudflare's Astro integration improves significantly, we might switch deployment platforms.
- AI-powered content tools -- we are experimenting with using Claude to assist in drafting blog post outlines (though all final content is human-written and human-edited).
- Database for CRM -- if we outgrow Airtable, we will likely move to a Postgres database with a custom admin interface rather than adopting a full CRM platform.
The Meta-Lesson
The most important thing about our tech stack is not the specific tools. It is the decision-making framework:
- Start with the problem, not the tool. We did not choose Astro because it is cool. We chose it because our site is content-heavy and benefits from static generation.
- Minimize ongoing cost. Every dollar spent on infrastructure is a dollar not spent on people or marketing. Our near-zero hosting cost is intentional.
- Optimize for the team you have. We are a small team. Tools that require dedicated DevOps (Kubernetes, self-hosted CI) do not make sense for us.
- Be willing to use different tools for different contexts. We use Astro for our site and Next.js for client projects. We use vanilla CSS for our site and Tailwind for client projects. The best tool depends on the context.
- Document your decisions. This blog post is itself a form of documentation. When a new team member asks "why do we use Astro?" we point them here instead of explaining it again.
That is our stack. It is not the right stack for everyone, but it is the right stack for us in 2026. Ask me again next year and some of it will have changed. The principles behind the choices will not.
Anurag Verma is the Founder and CEO of CODERCOPS, an AI-first tech studio. Our site runs on Astro, Vercel, and vanilla CSS -- and it is faster than most sites built with more expensive tools. See for yourself: codercops.com
Comments