Skip to main content

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.
CollectionSchema Key FeaturesExample Fields
servicesContact info, location, verificationtitle, category, contact, location, verified
eventsDate, organizer, panelists, registrationdate, location, organizer, panelists, registration
jobsJob type, skills, deadline, contacttype, category, skills, deadline, salary
resourcesFile metadata, languages, downloadsfileType, fileSize, languages, downloadUrl
newsCategory, author, business infocategory, author, businessName, tags
photosPhotographer, date, galleryphotographer, date, tags, gallery
marketplaceVendor, price, deliveryvendor, price, priceType, deliveryOptions
storesMenu, hours, payment methodstype, 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

API Response Format

{
  "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

1

Content Processing

Astro loads all MDX files from content collections and validates them against Zod schemas
2

Static Generation

Pages are pre-rendered to static HTML with minimal JavaScript
3

Asset Optimization

Images are optimized, CSS is minified, JavaScript is bundled
4

API Routes

Serverless functions are generated for API endpoints
5

Deployment

Static files are deployed to Netlify CDN, functions to AWS Lambda

Performance Optimizations

  • 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

1

Content Creation

Authors create/edit MDX files in src/content/ directories
2

Schema Validation

Frontmatter is validated against Zod schemas during development
3

Local Preview

Changes are visible immediately in development server
4

Git Commit

Content is committed to Git repository
5

Automated Build

Netlify detects changes and triggers build process
6

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

Extending the Platform

Adding a New Content Collection

1

Define Schema

Add schema to src/content.config.ts:
const myCollectionSchema = z.object({
  title: z.string(),
  description: z.string(),
  // ... other fields
});
2

Create Collection

const myCollection = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/content/mycollection" }),
  schema: myCollectionSchema,
});
3

Export Collection

export const collections = {
  mycollection: myCollection,
  // ... existing collections
};
4

Create Content Directory

Create src/content/mycollection/ directory
5

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

Performance

  • 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

Check that all MDX files have required frontmatter fields and correct data types. Review src/content.config.ts for schema requirements.
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.
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