Production-ready testimonial collection with Google Sheets integration
Features • Quick Start • CI/CD • Deployment
Conversational testimonial form built with Next.js 16 App Router, featuring chat-style UI with typing animations. Data syncs automatically to Google Sheets.
Originally created for Davince Band, fully customizable for any testimonial collection use case.
Live Demo: [Coming soon]
- 🚀 Modern Stack - Next.js 16, React 19, TypeScript, Tailwind CSS 4, Biome
- 💬 Chat Interface - Conversational flow with realistic typing animations
- ⚡ Performance - Memoized components, debounced scrolling, optimized re-renders
- 📊 Google Sheets - Automatic data sync, no database required
- 🔒 Secure - Environment validation, CSP headers, server-side credentials
- 🎨 Themeable - Dark mode, customizable design tokens
- 📱 Responsive - Mobile-first design, optimized for all screen sizes
- ♿ Accessible - WCAG compliant, keyboard navigation
User Experience
- Multi-step chat flow with typing animations
- Character count (250 limit) with live feedback
- Optional internal feedback field (not published)
- Review screen with inline editing
- Responsive layout optimized for mobile and desktop
- Comprehensive loading & error states
Technical
- Server-side Google Sheets API integration
- Build-time environment validation
- Type-safe error handling with custom error classes
- Optimized image formats (AVIF/WebP)
- Security headers (CSP, X-Frame-Options, HSTS)
- Pre-commit hooks with Husky + lint-staged
Prerequisites: Bun ≥1.0 or Node.js ≥24
# Clone & install
git clone https://github.com/rafactx/nextjs-testimonial-form.git
cd nextjs-testimonial-form
bun install
# Configure environment
cp .env.local.example .env.local
# Edit .env.local with your Google Sheets credentials
# Start dev server
bun devComplete configuration guide
- Go to Google Cloud Console
- Create project → APIs & Services → Credentials
- Create Credentials → Service Account
- Complete form → Create → Skip permissions → Done
- Select service account → Keys tab
- Add Key → Create new key → JSON
- Download and store securely (never commit to git)
- APIs & Services → Library
- Search "Google Sheets API" → Enable
- Create new Google Sheet
- Share with service account email (from JSON) as Editor
- Copy spreadsheet ID from URL:
docs.google.com/spreadsheets/d/{ID}/edit
Map JSON values to .env.local:
GOOGLE_SHEETS_CLIENT_EMAIL=your-account@project.iam.gserviceaccount.com
GOOGLE_SHEETS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYour_Key\n-----END PRIVATE KEY-----\n"
GOOGLE_SHEETS_SPREADSHEET_ID=your-spreadsheet-id
GOOGLE_SHEETS_SHEET_NAME=Sheet1\n in PRIVATE_KEY (they're line breaks, not literal text)
The repository includes a CI workflow (.github/workflows/ci.yml) that runs on push/PR:
- Lint & Check - Biome linting, formatting, and type checking
- Build - Next.js production build with mock env vars
No secrets needed for CI - build uses mock values to validate env var existence.
For production deployments on Vercel/other platforms:
-
GitHub Environments (recommended for security):
- Settings → Environments → New environment (e.g., "Production")
- Add secrets:
GOOGLE_SHEETS_CLIENT_EMAILGOOGLE_SHEETS_PRIVATE_KEYGOOGLE_SHEETS_SPREADSHEET_IDGOOGLE_SHEETS_SHEET_NAME
- Configure Required reviewers for approval gate
- Enable Branch protection on
main
-
Vercel Integration:
- Link GitHub repo to Vercel project
- Add environment variables in Vercel dashboard
- Auto-deploy on push to
main
# Development
bun dev # Start dev server with hot reload
bun build # Production build
bun start # Production server
# Code Quality
bun lint # Run Biome linter
bun lint:fix # Auto-fix Biome issues
bun format # Format code with Biome
bun format:check # Verify formatting
bun check # Lint + format + fix (pre-commit)
bun ci # CI mode (strict, no fixes)Steps:
- Import repository on vercel.com
- Add environment variables (see Google Sheets Setup)
- Deploy
CLI:
bun add -g vercel
vercel login
vercel --prodWorks with any Next.js-supporting platform:
- Netlify - Use Next.js Runtime plugin
- Railway - Auto-deploy from Git
- Render - Connect repository
- AWS Amplify - Git-based deployment
testimonial-form/
├── app/ # Next.js App Router
│ ├── api/testimonials/ # Submission endpoint (POST)
│ ├── layout.tsx # Root layout + ThemeProvider
│ └── page.tsx # Main page
├── components/
│ ├── ui/ # Base components (shadcn/ui)
│ └── testimonial-form.tsx # Main form (585 lines)
├── lib/
│ ├── env.ts # Build-time validation
│ ├── errors.ts # Type-safe error classes
│ ├── google-sheets.ts # Sheets API client
│ └── utils.ts # Utilities (cn helper)
└── .github/workflows/ # CI pipeline
Variáveis de ambiente faltando
- Cause: Missing required env vars
- Fix: Verify
.env.localhas all 4 variables from Google Sheets Setup - CI: Use mock values (already configured in ci.yml)
Error: Could not load the default credentials
- Cause: Invalid service account credentials
- Fix: Re-download JSON key, verify
PRIVATE_KEYincludes\ncharacters
API Request failed with status 403
- Cause: Service account lacks spreadsheet access
- Fix: Share spreadsheet with service account email as Editor
Error: Unable to parse range
- Cause:
SHEET_NAMEdoesn't match actual sheet name - Fix: Check sheet tab name in Google Sheets (default: "Sheet1")
Pre-commit hook not running
- Fix:
chmod +x .husky/pre-commit && git add .husky/pre-commit
Biome errors on CSS files
- Expected: Biome skips CSS (Tailwind not supported), only lints TS/JS/JSON
Port 3000 already in use
- Fix:
lsof -ti:3000 | xargs kill -9or change port:bun dev -- -p 3001
Modify design tokens in app/globals.css:
@theme inline {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
/* Add custom colors */
}Edit conversation flow in components/testimonial-form.tsx:
const botMessages: Record<Step, string[]> = useMemo(() => ({
name: ["Custom greeting!", "What's your name?"],
testimonial: ["Tell us about your experience..."],
// ...
}), [])Testimonials saved to Google Sheets:
| Timestamp | Name | Testimonial | Internal Feedback |
|---|---|---|---|
| 2025-12-27 10:30 | John Doe | Amazing! | Feature request X |
Optimizations:
- Component memoization (
React.memo) - Callback stabilization (
useCallback) - Derived state memoization (
useMemo) - Debounced scroll (100ms timeout)
- Next.js Image (AVIF/WebP with blur placeholder)
- Tree-shaking (optimized imports)
- Gzip/Brotli compression
Metrics (estimated):
- First Contentful Paint: < 1.5s
- Time to Interactive: < 3.5s
- Cumulative Layout Shift: < 0.1
See CONTRIBUTING.md for guidelines.
Quick start:
- Fork repository
- Create feature branch:
git checkout -b feature/name - Make changes + add Biome checks pass
- Commit with Conventional Commits
- Push and open PR
MIT License - see LICENSE
- Next.js - React framework
- Biome - Fast linter & formatter
- shadcn/ui - Component system
- react-type-animation - Typing effects
- Google Sheets API - Data storage