Day 04: Spam Protection & Deployment Switch

Today started with adding spam protection to the assessment form using Cloudflare Turnstile, but quickly evolved into a bigger architectural decision - switching from Sevalla Static Sites to Vercel to enable secure server-side form handling.

Try it yourself: Assessment Form

What Got Done

Cloudflare Turnstile Integration

Integrated Cloudflare Turnstile as a privacy-friendly CAPTCHA alternative:

  • Widget Rendering: Turnstile widget loads dynamically on the final assessment step
  • Token Validation: Submission requires a valid Turnstile token before proceeding
  • Error Handling: Graceful error states if verification fails
  • Environment Configuration: Site key configured via PUBLIC_TURNSTILE_SITE_KEY environment variable

Implementation Details

The Turnstile integration lives in the Assessment.tsx component:

// Load Turnstile script and render widget on final step
useEffect(() => {
  const isLastStep = state.currentStep === totalQuestions;
  
  if (!isLastStep) {
    // Clean up widget if navigating away from last step
    if (turnstileWidgetId.current && window.turnstile) {
      window.turnstile.remove(turnstileWidgetId.current);
      turnstileWidgetId.current = null;
    }
    return;
  }

  // Load and render Turnstile widget
  renderTurnstile();
}, [state.currentStep, totalQuestions]);

Key features:

  • Widget only appears on the final step
  • Proper cleanup when navigating away
  • Token captured via callback
  • Submit button disabled until verification completes

Testing

Added unit tests to verify Turnstile integration:

  • Widget renders on final step
  • Submission requires valid token
  • Error states handled properly
  • Token capture works correctly

The Deployment Platform Switch

Why the Change?

The original plan was to deploy on Sevalla Static Sites (free tier), but Turnstile integration revealed a critical limitation: Sevalla Static Sites don’t support server-side rendering (SSR) or serverless functions.

This created a security problem:

The Issue:

  • Turnstile tokens must be verified server-side for security
  • Sevalla Static Sites are purely static - no SSR support
  • Sevalla Applications (which support SSR) are not free
  • Without SSR, I’d need to expose API keys in the browser
  • Or use a third-party proxy like Formspree (limited to 50 submissions/month)
  • Neither option was acceptable for a production site

The Solution: Switch to Vercel, which offers:

  • ✅ Full SSR support with Astro
  • ✅ Serverless functions for API routes
  • ✅ Server-side Turnstile verification
  • ✅ Secure Resend API integration
  • ✅ 1 million function invocations/month (free tier)
  • ✅ All API keys stay server-side
  • ✅ Still completely free

What Changed

Configuration:

  • Added @astrojs/vercel adapter with output: 'server'
  • Static pages marked with export const prerender = true for performance
  • Created /api/submit serverless function for form handling

Form Submission Flow:

Browser → /api/submit (serverless) → Verify Turnstile → Resend API → Email

Security Improvements:

  1. Server-side Turnstile verification - tokens verified before processing
  2. API keys never exposed - all secrets stay on server
  3. No client-side API calls - no risk of key exposure in browser

Cost Comparison

Sevalla Static Sites (Free):

  • 600 build minutes/month
  • 100 GB bandwidth/month
  • ❌ No serverless functions
  • ❌ No SSR support

Sevalla Applications (Paid):

  • ✅ SSR support
  • ✅ Serverless functions
  • 💰 Not free (would need to upgrade)

Vercel Hobby (Free):

  • 1 million function invocations/month
  • 4 hours Active CPU time/month
  • 360 GB-hours Provisioned Memory/month
  • 100 GB bandwidth/month
  • ✅ Full SSR + serverless functions
  • ✅ Completely free

The Vercel free tier is more than sufficient for this project’s needs, and keeps the entire project cost at $0.

Environment Setup

To use Turnstile with Vercel, you need to:

  1. Get your site key from Cloudflare Dashboard
  2. Add to Vercel environment variables:
    PUBLIC_TURNSTILE_SITE_KEY=your_site_key_here
    TURNSTILE_SECRET_KEY=your_secret_key_here
    RESEND_API_KEY=your_resend_api_key_here

Important: Don’t use PUBLIC_ prefix for secrets (TURNSTILE_SECRET_KEY, RESEND_API_KEY).

Lessons Learned

  1. Platform limitations matter - Always verify your hosting platform supports your architecture before committing
  2. Security first - Never compromise on security for convenience or cost
  3. Free tiers can be generous - Vercel’s free tier is actually more capable than many paid static hosting options
  4. SSR isn’t always needed - But when you need it, you really need it

What’s Next

With the deployment platform sorted and Turnstile integrated, Day 5 will focus on completing the form submission flow and testing the entire assessment experience end-to-end.