LekkerSpots - Western Cape Hidden Gems
A vibrant travel discovery web application showcasing hidden gems and local favorites in the Western Cape, South Africa. Discover the best coffee shops, restaurants, beaches, hikes, markets, and bars with an interactive map and beautiful visual design.
- Interactive Map: Explore locations on an interactive Leaflet map with custom markers
- Smart Search: Full-text search across location names, descriptions, neighborhoods, and tags
- Category Filtering: Browse locations by type with colorful gradient category badges
- Dynamic Tags: Filter by multiple tags with vibrant, color-coded badges
- Responsive Design: Beautiful mobile-first design that works on all devices
- Admin Dashboard: Secure admin panel for content management
- Multi-Image Upload: Upload multiple images per location with Google Cloud Storage integration
- Newsletter Integration: Custom newsletter subdomain (newsletter.lekkerspots.co.za) powered by Beehiiv with celebratory confetti animation and content tag display
- Continue Your Adventure: Related spots recommendations on location pages for improved engagement and SEO
- Insider Tips: FAQ-style tips (WiFi, parking, pet policies) with JSON-LD structured data for rich search results
- News Ticker: Animated announcements banner on homepage with admin management
- Dark Mode: Full dark mode support with accessible color contrast
- Accessibility: Respects
prefers-reduced-motionand maintains WCAG-compliant contrast ratios
LekkerSpots features a vibrant sunset color palette inspired by the Western Cape's natural beauty:
- Turquoise Primary: Reflects the Atlantic Ocean and Table Bay
- Hot Pink Accent: Captures the energy of stunning sunsets
- Coral/Orange Secondary: Echoes golden hour along the beaches
- Gradient Category Badges: Each category has its own colorful gradient (orange for coffee shops, pink for restaurants, teal for beaches, etc.)
- Photography-First: Hero section features professional Western Cape imagery with elegant Lottie animations
- React 18 with TypeScript
- Vite for fast development and building
- Wouter for lightweight client-side routing
- Shadcn/ui component library (Radix UI primitives)
- Tailwind CSS for styling
- TanStack Query for server state management
- Leaflet.js for interactive maps
- Lottie React for smooth animations
- Uppy for file uploads
- Node.js with Express.js
- TypeScript throughout the stack
- Drizzle ORM for type-safe database queries
- Passport.js for authentication (Local Strategy)
- PostgreSQL (Neon serverless) for data persistence
- Google Cloud Storage for image hosting
- Clone the repository:
git clone <repository-url>
cd lekker-spots- Install dependencies:
npm install-
Set up environment variables (see Environment Variables section below)
-
Set up the database:
npm run db:push- Start the development server:
npm run devThe application will be available at http://localhost:5000
Create a .env file in the root directory with the following variables:
# Database
DATABASE_URL=your_postgresql_connection_string
# Admin Authentication
ADMIN_USERNAME=your_admin_username
ADMIN_PASSWORD_HASH=your_bcrypt_hashed_password
# Session Secret
SESSION_SECRET=your_random_session_secret
# Google Cloud Storage
GCS_BUCKET_NAME=your_gcs_bucket_name
GCS_PROJECT_ID=your_gcs_project_id
GOOGLE_APPLICATION_CREDENTIALS=path_to_service_account_json
# Newsletter (Optional)
BEEHIIV_API_KEY=your_beehiiv_api_key
BEEHIIV_PUBLICATION_ID=your_publication_idnode -e "console.log(require('bcrypt').hashSync('your_password', 10))"βββ client/ # Frontend React application
β βββ src/
β β βββ components/ # Reusable UI components
β β βββ pages/ # Page components
β β βββ lib/ # Utilities and helpers
β β βββ hooks/ # Custom React hooks
βββ server/ # Backend Express application
β βββ routes.ts # API route definitions
β βββ storage.ts # Database interface
β βββ index.ts # Server entry point
βββ shared/ # Shared types and schemas
β βββ schema.ts # Database schema and Zod types
βββ attached_assets/ # Static assets (images, animations)
- Full-text search using PostgreSQL
ILIKEfor fuzzy matching - Multi-tag filtering with URL-driven state
- Category-based browsing with gradient badges
- Real-time results with no page refresh
- Multi-image upload with drag-and-drop support via Uppy
- Drag-and-drop reordering to control preview image selection
- Individual delete controls with visible X buttons
- Visual "Preview Image" badge on the first image
- Automatic cloud storage via Google Cloud Storage
- Pre-signed URLs for secure uploads
- Image gallery with thumbnail navigation
- Real-time visual feedback during drag operations
- Secure session-based authentication
- Full CRUD operations for locations
- Rich form validation with Zod schemas
- Image upload management
- Interactive Leaflet map with OpenStreetMap tiles
- Custom markers for different categories
- Click markers to view location details
- Responsive map controls
- New Feature: Story cards on the homepage now display content tags from Beehiiv posts
- How It Works: Tags assigned in Beehiiv (e.g., "soul searching", "trail running", "farm stay") are automatically pulled via the Beehiiv API and displayed as badges on story cards
- Display: Tags appear as subtle secondary badges above the publish date on each story card
- API: The
/api/storiesendpoint now returns atagsarray with content tag strings from Beehiiv'scontent_tagsfield - Inspiration: Similar to Milkroad's newsletter tag display system
- Change: Migrated newsletter links from Beehiiv default subdomain to custom subdomain
- Old URL:
https://lekkerspots.beehiiv.com/ - New URL:
https://newsletter.lekkerspots.co.za/ - Updated Components: Header navigation, Footer links, Stories section
- SEO: Added newsletter URL to JSON-LD structured data (
sameAsarray) for better brand recognition by search engines and AI crawlers - Impact: Improved branding consistency with official LekkerSpots domain
- New Feature: Added "Continue Your Adventure" section to location detail pages
- Purpose: Reduces bounce rate and improves SEO through internal linking between related locations
- Admin Control: Admins can manually curate 2-3 related nearby spots for each location via multi-select dropdown in the edit form
- Display: Related spots appear below the Insider Tips section with category badges and direct links
- SSR Support: Related locations are server-side rendered for SEO crawlers, ensuring Google can see and index the internal links without JavaScript execution
- API Endpoint:
GET /api/locations/:id/relatedreturns related location data (blocked by robots.txt, but SSR ensures crawler visibility) - Database: Added
relatedLocationIdstext array column to locations table
- Issue: Duplicate content in Google Search Console caused by www vs non-www URL variations
- Impact: Search engines saw identical content at both
www.lekkerspots.co.zaandlekkerspots.co.za, diluting SEO authority - Solution: Enforced canonical domain (
https://lekkerspots.co.za) across all URL generation:- Server-side: sitemap.xml, robots.txt, and all middleware now hardcode canonical domain in production
- Client-side: React Helmet meta tags use canonical constant instead of window.location
- Development: Dynamic host resolution preserved for local testing
- Result: All canonical tags, Open Graph URLs, and sitemap entries consistently reference non-www domain
- Verified: Google Search Console URL Inspection confirms canonical tags work correctly regardless of access method (www or non-www)
- See
PRODUCTION_DEPLOYMENT_FIX.mdfor technical details
- Drag-and-Drop Reordering: Admin can now reorder location images by dragging and dropping them
- Preview Image Control: The first image in the array serves as the preview thumbnail on location cards, clearly marked with a "Preview Image" badge
- Enhanced Delete Buttons: Larger, more visible delete buttons (red X icons) on each image for easy removal
- Visual Feedback: Clear visual indicators during drag operations (reduced opacity, blue border, scale effect)
- Improved UX: Better spacing, layout, and instructional text to guide admins
- Bug Fix: Resolved issue where delete buttons were hidden by
overflow-hiddenCSS on rounded containers
- Issue: Sitemap.xml endpoint was being intercepted by Vite's catch-all route, returning HTML instead of XML
- Root Cause: SEO routes (sitemap.xml, robots.txt) were registered before Vite middleware setup, causing route precedence issues
- Solution:
- Moved sitemap and robots.txt route handlers to execute after registerRoutes() but before Vite middleware
- Added explicit skip logic in htmlMetaRewriter middleware to bypass /sitemap.xml and /robots.txt
- Routes now serve proper XML/text content in both development and production
- Impact: Sitemap now works correctly for all SEO crawlers (Ahrefs, Google, Bing) while preserving social preview functionality
- Verification: Tested in production with working XML sitemap at https://lekkerspots.co.za/sitemap.xml
- Critical Fix: Resolved social media preview issue where
__BASE_URL__placeholders weren't being replaced in production - Root Cause:
express.staticmiddleware uses internalsendmodule, bypassingres.sendFileoverrides - Solution: Rewrote middleware to directly intercept and serve processed HTML before express.static
- Impact: Social media previews now work perfectly on all platforms (Slack, Facebook, Twitter, LinkedIn, Discord)
- Verified: Both main domain (lekkerspots.co.za) and www subdomain working reliably
- See
PRODUCTION_DEPLOYMENT_FIX.mdfor detailed technical analysis
- Server-Side Meta Tag Injection: Location pages now inject location-specific meta tags server-side for social crawlers
- Dynamic Meta Tags: Each location page serves unique title, Open Graph tags, Twitter Card tags, and JSON-LD structured data
- Social Media Preview: Tested and working on Facebook, Twitter, LinkedIn, Slack, Discord, and WhatsApp
- Custom OG Image: Professional 1200Γ630px image featuring Western Cape beach with LekkerSpots logo
- Favicon System: Complete multi-size favicon implementation
- Browser icons: 16x16, 32x32, favicon.ico
- iOS: 180x180 apple-touch-icon
- Android: 192x192 and 512x512 icons with web manifest
- Security: HTML escaping for user-generated content prevents XSS attacks in meta tags
- Dynamic Base URL: Works seamlessly across development and production environments
- Fixed sitemap to use production domain (lekkerspots.co.za) for optimal SEO
- Dynamic sitemap generation with automatic updates from database
- Proper robots.txt configuration for search engine crawling
- All sitemap URLs now correctly reflect the production domain
- Vibrant sunset gradient palette (turquoise, hot pink, coral, yellow)
- Gradient category badges for visual distinction
- Colorful tag system using theme colors
- Full dark mode support with accessible contrast
- Gradient logo text in header
- Professional Western Cape stock photo background
- Area-map Lottie animation as accent icon
- Gradient overlay for optimal text readability
- Clean visual hierarchy
- Reusable
LottieAnimationcomponent - Three custom animations: area-map (hero), confetti (newsletter), empty-state (search)
- Accessibility support with
prefers-reduced-motion - Confetti celebration on newsletter signup
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License.
- Stock photos from professional photography sources
- Lottie animations for smooth UI interactions
- Shadcn/ui for beautiful, accessible components
- OpenStreetMap for map tiles
- The Western Cape community for inspiration
Built with β€οΈ for Western Cape explorers
