Published
- 6 min read
Building a Minimalistic Site Builder for Car Salespeople

Introduction
Car salespeople need websites that are customizable, SEO-friendly, and easy to maintain to generate leads effectively. After discussing with a sales colleague, I learned that many rely on freelancers to build WordPress sites, which are often rigid, slow, and rank poorly on search engines, resulting in fewer leads.
To address this, I built a minimalistic site builder tailored for car salespeople, with a focus on performance, SEO, and ease of use.
Initially, I created a static site, but manual updates for minor changes like meta titles were time-consuming, and a frequent request from my customers. This led to the development of a dynamic solution with an admin dashboard, empowering salespeople to manage their sites without technical expertise.
Features
SEO and Performance
- Technical SEO Best Practices: Dynamic meta tags, structured data, and a auto-generated
sitemap.xml
boost search engine rankings. - 90+ PageSpeed Insights Score: Achieved through Server-Side Rendering (SSR) with Tanstack Start and an advanced image optimization pipeline.
- Google Analytics and Tag Manager: Easily add tracking tags via the admin dashboard for visitor insights.
Admin Dashboard
- User Authentication: Secure login with Clerk for managing site content.
- Homepage Customization:
- Edit metadata (title, description, keywords) for SEO.
- Customize the hero section with desktop and mobile images, titles, and call-to-action buttons.
- Add and reorder sections (e.g., feature sliders, banners) using a drag-and-drop interface powered by
@dnd-kit
.
- Car Models Page:
- Add car models with interactive features like color pickers.
- Upload galleries and specifications to showcase car designs.
- Contact Info Management:
- Update WhatsApp link, company phone, address, and social media links (Facebook, Instagram, X, YouTube).
- Embed Google Maps with customizable location queries.
- Form Inquiry Management: Track and respond to lead submissions, with status tracking (e.g., “new”, “contacted”).
- About Us and Articles:
- Edit rich content with a Tiptap WYSIWYG editor, supporting images, links, and formatted text.
- Publish blog posts to engage visitors and improve SEO.
Customization and Branding
- Section Components:
- Feature Sections: Display a slider of images with titles, descriptions, and feature lists, optimized for desktop and mobile.
- Banner Sections: Full-width banners for promotions or Canva-designed visuals.
- Company Logo: Upload logos (standard and white versions) to reinforce branding.
- Custom Domains: Connect a custom domain for a professional look.
Technical Challenges
Dynamic Homepage Schema
The homepage’s dynamic sections required a flexible database schema. Using Drizzle ORM with SQLite, I designed a homepage_feature_sections table to support various section types (e.g., feature sliders, banners) with a polymorphic design. The section_type field (e.g., ‘default’, ‘feature_cards_grid’) determines the section’s purpose, while type_specific_data (stored as JSON) holds configuration unique to each type. Additional fields like desktop_image_urls, mobile_image_urls, features, and button links (e.g., primary_button_link) enable rich customization. This structure allows users to add, reorder, and edit sections via the drag-and-drop interface without schema changes, though optimizing JSON parsing and query performance for complex sections remains a challenge.
export const homepageFeatureSections = sqliteTable(
"homepage_feature_sections",
{
...
// New field for section type
sectionType: text("section_type").notNull().default("default"), // e.g., 'default', 'feature_cards_grid'
order: integer("order").notNull(), // For ordering sections
typeSpecificData: text("type_specific_data", {
mode: "json",
}),
...
}
);
Image Optimization
Images are critical for showcasing car designs, but large files can slow down sites. I implemented the following pipeline:
- In-Browser Compression: Images larger than 2MB are compressed to 1MB with a maximum width of 1920px using Cloudflare’s Image API.
- ResponsiveImage Component: Automatically generates
srcset
for mobile (640px) and thumbnail (320px) versions, reducing load times. - Presigned URLs: Securely upload images to Cloudflare R2 storage with consistent naming (e.g.,
filename_mobile.jpg
).
Balancing SEO and Customizability
To ensure high customizability without sacrificing SEO, I used Tantack Start for SSR and TanStack Router for dynamic routing. Metadata fields (meta_title
, meta_description
, meta_keywords
) are stored in the database and editable via the admin dashboard, ensuring search engines index the latest content. A generated sitemap.xml
further enhances crawlability.
Tech Stack
- Frontend/Backend: Tanstack Start for SSR, dynamic routing, and server functions
- DB: Node.js with Drizzle ORM and SQLite for lightweight data storage (One DB per site)
- Authentication: Clerk for user management
- Storage: Cloudflare R2 for images, with presigned URLs for uploads
- Image Processing: Cloudflare Image API for compression and resizing
- Styling: Tailwind CSS
- Content Editing: Tiptap WYSIWYG editor to create/edit articles and About Us pages.
- Forms: React Hook Form with Zod validation for handling
- Deployment: Digital Ocean VPS
- CI/CD: Github webhook with custom script to pull, install deps, build, and restart PM2 app
Deployment Insights
Initially, there’s only one customer. Updating one site is simple. Whenever a code push is made, I would ssh to the VPS, git pull, pnpm i, pnpm build, and then pm2 restart app. However, for each new customer, I had to git clone the same repo per customer, meaning a pull is required for each new changes, which requires another rebuild and pm2 restart whenever new changes are made.
Eventually, I had an insight that one repo copy is all that’s needed. For each new customer, a new pm2 app is created that runs a script, connected to a new sqlite db specific to the customer.
original app:
subsequent apps:
The final touch is to integrate GitHub webhooks on push events. These changes made the continuous deployment flow much faster.
SEO insights
Sitemaps can help speed up google’s indexing process to quickly publish pages to search results. This is why it’s worth tackling the feature to generate this. The challenge is that new pages can be created in the site builder, like new articles and new car models. Statically generating run into the risk of becoming stale. Therefore, I created an GET endpoint for generating sitemaps, fetching the latest article and model entries.
Perhaps there’s a point where more and more articles are created such that fetching for these articles on request may be too slow. But we’ll see.
Moving Forward
The following tasks could be considered in the future:
- Promote the Offering: Build a marketing site with SEO-optimized content to attract organic leads.
- Automate Onboarding: Integrate a payment gateway (e.g., Stripe, Midtrans) to allow customers to sign up and automatically spin up new instances under subdomains (e.g.,
customer.carsalessite.com
). - Domain Onboarding: Feature for customers to connect their own domain, which requires auto updating the Caddy config, auto-creating R2 bucket with correct CORS settings, and auto-creating Google analytics connection
Learning Lessons
- User Feedback Drives Success: Iterating based on target customer decreases assumptions on which features to work on, and which to prioritize.
- Performance is Paramount: The image optimization pipeline and SSR significantly improved user experience and SEO rankings.
- Simplicity Wins: A minimalistic admin dashboard with drag-and-drop functionality made the tool accessible to non-technical users.