Platform Overview
Dzaleka Online Services is built on a modern, scalable architecture designed to serve the refugee community efficiently while remaining maintainable by non-technical contributors.
Architecture Overview
Technology Stack
Astro 5.x Static site generator with partial hydration for optimal performance
React 18 Interactive components for dynamic functionality
Tailwind CSS Utility-first CSS framework with custom design system
MDX Markdown with JSX for content authoring
Core Dependencies
{
"dependencies" : {
"astro" : "^5.16.6" ,
"@astrojs/react" : "^4.4.2" ,
"@astrojs/tailwind" : "^6.0.2" ,
"@astrojs/mdx" : "^4.3.13" ,
"@astrojs/netlify" : "^6.6.3" ,
"react" : "^18.2.0" ,
"react-dom" : "^18.2.0" ,
"tailwindcss" : "^3.3.3" ,
"zod" : "^3.22.2" ,
"minisearch" : "^7.1.1" ,
"leaflet" : "^1.9.4" ,
"date-fns" : "^2.30.0" ,
"alpinejs" : "^3.13.3"
}
}
Project Structure
dzaleka-online-services/
├── src/
│ ├── components/ # Reusable UI components
│ │ ├── ui/ # Base UI components (Button, Card, etc.)
│ │ ├── Header.astro # Site header
│ │ ├── Footer.astro # Site footer
│ │ └── ... # Feature-specific components
│ ├── content/ # Content collections (MDX files)
│ │ ├── services/ # Service directory entries
│ │ ├── events/ # Event listings
│ │ ├── jobs/ # Job postings
│ │ ├── resources/ # Resource library
│ │ ├── news/ # News and announcements
│ │ ├── photos/ # Photo gallery
│ │ ├── stories/ # Community stories
│ │ ├── artists/ # Artist profiles
│ │ ├── dancers/ # Dancer profiles
│ │ ├── poets/ # Poet profiles
│ │ ├── marketplace/ # Marketplace listings
│ │ ├── stores/ # Store profiles
│ │ └── ... # Additional collections
│ ├── layouts/ # Page layouts
│ │ ├── MainLayout.astro # Main site layout
│ │ ├── DocsLayout.astro # Documentation layout
│ │ ├── ServiceLayout.astro # Service page layout
│ │ └── ... # Other layouts
│ ├── pages/ # Route pages
│ │ ├── index.astro # Homepage
│ │ ├── services/ # Services pages
│ │ ├── events/ # Events pages
│ │ ├── jobs/ # Jobs pages
│ │ ├── api/ # API endpoints
│ │ └── ... # Other pages
│ ├── styles/ # Global styles
│ ├── utils/ # Utility functions
│ ├── types/ # TypeScript types
│ ├── data/ # Static data files
│ └── content.config.ts # Content collection schemas
├── public/ # Static assets
│ ├── images/ # Image files
│ └── ... # Other static files
├── astro.config.mjs # Astro configuration
├── tailwind.config.cjs # Tailwind configuration
├── tsconfig.json # TypeScript configuration
└── netlify.toml # Netlify deployment config
Content Collections Architecture
Content Collection Definition
Content is managed through Astro’s Content Collections with Zod schemas for type safety:
// src/content.config.ts
import { defineCollection , z } from 'astro:content' ;
import { glob } from 'astro/loaders' ;
// Service schema
const serviceSchema = z . object ({
title: z . string (),
description: z . string (),
category: z . string (),
featured: z . boolean (). optional (),
verified: z . boolean (). optional (),
logo: z . string (). optional (),
image: z . string (). optional (),
contact: z . object ({
email: z . string (). optional (),
phone: z . string (). optional (),
whatsapp: z . string (). optional (),
hours: z . string (). optional (),
}). optional (),
location: z . object ({
address: z . string (),
city: z . string (),
coordinates: z . object ({
lat: z . number (),
lng: z . number (),
}). optional (),
}). optional (),
socialMedia: z . object ({
website: z . string (). optional (),
facebook: z . string (). optional (),
instagram: z . string (). optional (),
twitter: z . string (). optional (),
linkedin: z . string (). optional (),
}). optional (),
tags: z . array ( z . string ()). optional (),
lastUpdated: z . date (). optional (),
status: z . enum ([ 'active' , 'inactive' ]). optional (). default ( 'active' ),
});
const servicesCollection = defineCollection ({
loader: glob ({ pattern: "**/*.md" , base: "./src/content/services" }),
schema: serviceSchema ,
});
export const collections = {
services: servicesCollection ,
// ... other collections
};
Content Collections Overview
All content collections use the same pattern: Markdown files with frontmatter validated by Zod schemas.
Collection Schema Key Features Example Fields services Contact info, location, verification title, category, contact, location, verifiedevents Date, organizer, panelists, registration date, location, organizer, panelists, registrationjobs Job type, skills, deadline, contact type, category, skills, deadline, salaryresources File metadata, languages, downloads fileType, fileSize, languages, downloadUrlnews Category, author, business info category, author, businessName, tagsphotos Photographer, date, gallery photographer, date, tags, gallerymarketplace Vendor, price, delivery vendor, price, priceType, deliveryOptionsstores Menu, hours, payment methods type, menu, hours, paymentMethods
Example Content File
---
title : BloomBox Design Labs
category : "Education"
description : BloomBox Design Labs is dedicated to the application of sustainable design strategies...
location :
address : BloomBox, Dzaleka, M16
city : Dowa
coordinates :
lat : -13.6628274
lng : 33.8704301
contact :
email : ''
phone : '+1 236-888-5055'
hours : 'Monday-Friday, 9:00 AM - 5:00 PM'
socialMedia :
twitter : 'https://x.com/roux_sofie'
instagram : 'https://www.instagram.com/bloomboxdesignlabs/'
website : 'https://www.bloomboxdesignlabs.com/'
featured : true
verified : true
lastUpdated : 2025-03-06
---
## About BloomBox Design Labs
BloomBox Design Labs specializes in design thinking, architecture, and capacity building...
API Architecture
API Endpoints
The platform provides RESTful API endpoints using Astro’s API routes:
// src/pages/api/events.ts
import type { APIRoute } from 'astro' ;
import { createGetHandler , createOptionsHandler , createPostHandler } from '../../utils/api-utils' ;
export const GET : APIRoute = createGetHandler ( 'events' );
export const OPTIONS : APIRoute = createOptionsHandler ();
export const POST : APIRoute = createPostHandler ( 'events' );
API Utilities
// src/utils/api-utils.ts
import type { APIRoute } from 'astro' ;
import { getCollection } from 'astro:content' ;
export const corsHeaders = {
'Access-Control-Allow-Origin' : '*' ,
'Access-Control-Allow-Methods' : 'GET, POST, OPTIONS' ,
'Access-Control-Allow-Headers' : 'Content-Type' ,
'Content-Type' : 'application/json'
};
// Rate limiting: 60 requests per minute
const RATE_LIMIT_WINDOW = 60 * 1000 ;
const MAX_REQUESTS_PER_WINDOW = 60 ;
export function createGetHandler ( collectionName : string ) : APIRoute {
return async ({ request }) => {
try {
const collection = await getCollection ( collectionName );
const processedData = processCollectionData ( collection , collectionName );
return new Response (
JSON . stringify ({
status: 'success' ,
count: processedData . length ,
data: { [collectionName]: processedData }
}),
{ status: 200 , headers: corsHeaders }
);
} catch ( error ) {
return new Response (
JSON . stringify ({
status: 'error' ,
message: `Failed to fetch ${ collectionName } `
}),
{ status: 500 , headers: corsHeaders }
);
}
};
}
Available API Endpoints
/api/services GET all services with filtering options
/api/events GET events with date filtering
/api/jobs GET job listings with status filters
/api/resources GET resources with category filters
/api/news GET news and announcements
/api/photos GET photo gallery metadata
{
"status" : "success" ,
"count" : 42 ,
"data" : {
"events" : [
{
"id" : "breaking-myths-about-refugees" ,
"collection" : "events" ,
"title" : "Breaking Myths About Refugees" ,
"date" : "2025-04-15T00:00:00.000Z" ,
"location" : "Dzaleka Community Center" ,
"category" : "Educational" ,
"organizer" : "Dzaleka Connect" ,
"status" : "upcoming"
}
]
}
}
Design System
Tailwind Configuration
// tailwind.config.cjs
module . exports = {
content: [ './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}' ],
theme: {
extend: {
colors: {
primary: {
50 : '#eff6ff' ,
100 : '#dbeafe' ,
// ... full color scale
},
sand: {
50 : '#faf9f7' ,
100 : '#f5f3f0' ,
// ... earth tones
},
terracotta: {
// ... warm accent colors
},
earth: {
// ... natural tones
},
ochre: {
// ... golden yellows
},
vibrant: {
// ... bright accents
}
},
fontFamily: {
sans: [ 'Inter' , 'system-ui' , 'sans-serif' ],
},
},
},
plugins: [
require ( '@tailwindcss/typography' ),
require ( '@tailwindcss/forms' ),
require ( '@tailwindcss/aspect-ratio' ),
],
};
Component Library
Base UI Components
<!-- src/components/ui/Button.astro -->
---
interface Props {
variant ?: 'primary' | 'secondary' | 'outline' ;
size ?: 'sm' | 'md' | 'lg' ;
href ?: string ;
}
const { variant = 'primary' , size = 'md' , href } = Astro.props;
---
< a href = { href } class = "btn btn-{variant} btn-{size}" >
< slot />
</ a >
Feature Components
Header : Site navigation with mobile menu
Footer : Links, contact info, social media
ServiceCard : Display service information
EventCard : Show event details with date badges
JobCard : Job listing with metadata
SearchBar : Real-time search with MiniSearch
Pagination : Navigate through content pages
CategoryFilter : Filter content by categories
Search Implementation
MiniSearch Integration
// Search index generation
import MiniSearch from 'minisearch' ;
const miniSearch = new MiniSearch ({
fields: [ 'title' , 'description' , 'category' , 'tags' ],
storeFields: [ 'title' , 'description' , 'category' , 'id' ],
searchOptions: {
boost: { title: 2 },
fuzzy: 0.2 ,
prefix: true
}
});
// Add documents from all collections
const services = await getCollection ( 'services' );
const events = await getCollection ( 'events' );
const jobs = await getCollection ( 'jobs' );
miniSearch . addAll ([
... services . map ( s => ({ id: s . id , ... s . data , type: 'service' })),
... events . map ( e => ({ id: e . id , ... e . data , type: 'event' })),
... jobs . map ( j => ({ id: j . id , ... j . data , type: 'job' }))
]);
// Search
const results = miniSearch . search ( 'education' , { fuzzy: 0.2 });
Deployment Architecture
Netlify Configuration
# netlify.toml
[ build ]
publish = "dist"
command = "npm run build"
[ functions ]
directory = "netlify/functions"
[[ redirects ]]
from = "/*"
to = "/index.html"
status = 200
[[ headers ]]
for = "/api/*"
[ headers . values ]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, POST, OPTIONS"
Access-Control-Allow-Headers = "Content-Type"
[ build . environment ]
NODE_VERSION = "20"
Build Process
Content Processing
Astro loads all MDX files from content collections and validates them against Zod schemas
Static Generation
Pages are pre-rendered to static HTML with minimal JavaScript
Asset Optimization
Images are optimized, CSS is minified, JavaScript is bundled
API Routes
Serverless functions are generated for API endpoints
Deployment
Static files are deployed to Netlify CDN, functions to AWS Lambda
Partial Hydration : Only interactive components load JavaScript
Image Optimization : Automatic responsive images with lazy loading
Code Splitting : Per-page JavaScript bundles
CDN Caching : Static assets cached globally
Prerendering : Most pages generated at build time
Data Flow
Content Management Workflow
Content Creation
Authors create/edit MDX files in src/content/ directories
Schema Validation
Frontmatter is validated against Zod schemas during development
Local Preview
Changes are visible immediately in development server
Git Commit
Content is committed to Git repository
Automated Build
Netlify detects changes and triggers build process
Deploy
New version is deployed to production automatically
Security Features
API Security
Rate Limiting : 60 requests per minute per IP
CORS Headers : Configured for safe cross-origin access
Input Validation : All API inputs validated with Zod
Error Handling : Safe error messages without exposing internals
Content Security
Schema Validation : All content validated before build
Sanitization : HTML and user inputs sanitized
HTTPS Only : All connections encrypted
CSP Headers : Content Security Policy headers configured
Monitoring & Analytics
Built-in Analytics
<!-- src/pages/analytics.astro -->
---
import { getCollection } from 'astro:content';
const services = await getCollection('services');
const events = await getCollection('events');
const jobs = await getCollection('jobs');
const stats = {
totalServices : services . length ,
totalEvents : events . length ,
totalJobs : jobs . filter ( j => j . data . status === 'open' ). length ,
// ... more statistics
} ;
---
Metrics Tracked
Total content items by collection
Active vs inactive services
Open vs closed job postings
Upcoming vs past events
Featured content counts
API usage statistics
Development Setup
Local Development
# Clone repository
git clone https://github.com/your-org/dzaleka-online-services.git
cd dzaleka-online-services
# Install dependencies
npm install
# Start development server
npm run dev
# Server runs at http://localhost:4321
# Build for production
npm run build
# Preview production build
npm run preview
Environment Variables
# .env
PUBLIC_SITE_URL=https://services.dzaleka.com
FORMSPREE_ENDPOINT=https://formspree.io/f/your-form-id
RESEND_API_KEY=your-resend-api-key
Adding a New Content Collection
Define Schema
Add schema to src/content.config.ts: const myCollectionSchema = z . object ({
title: z . string (),
description: z . string (),
// ... other fields
});
Create Collection
const myCollection = defineCollection ({
loader: glob ({ pattern: "**/*.md" , base: "./src/content/mycollection" }),
schema: myCollectionSchema ,
});
Export Collection
export const collections = {
mycollection: myCollection ,
// ... existing collections
};
Create Content Directory
Create src/content/mycollection/ directory
Add Pages
Create listing and detail pages in src/pages/mycollection/
Adding a New API Endpoint
// src/pages/api/mycollection.ts
import type { APIRoute } from 'astro' ;
import { createGetHandler , createOptionsHandler } from '../../utils/api-utils' ;
export const GET : APIRoute = createGetHandler ( 'mycollection' );
export const OPTIONS : APIRoute = createOptionsHandler ();
Best Practices
Content Authoring
Use descriptive filenames (lowercase, hyphens for spaces)
Include all required frontmatter fields
Optimize images before uploading (max 1MB)
Use relative paths for internal links
Test content locally before committing
Component Development
Keep components small and focused
Use TypeScript for type safety
Follow Astro’s component patterns
Minimize client-side JavaScript
Use semantic HTML
Lazy load images and heavy components
Minimize bundle sizes
Use partial hydration strategically
Optimize database queries in API routes
Cache API responses when appropriate
Troubleshooting
Common Issues
Build fails with schema validation error
Check that all MDX files have required frontmatter fields and correct data types. Review src/content.config.ts for schema requirements.
API returns 429 rate limit error
You’ve exceeded 60 requests per minute. Wait for the rate limit window to reset or implement request throttling.
Ensure images are in the public/images/ directory and paths start with /images/. Check file permissions and extensions.
Search not returning results
Verify search index is built correctly. Check that documents have searchable fields (title, description, tags).
Resources
For technical support or questions, contact the development team at dzalekaconnect@gmail.com