Skip to content
🟡In Arbeit56%
Vollständigkeit:
70%
Korrektheit:
70%
⏳ Noch nicht geprüft

Routing

Overview

The municipalities system in p2d2 uses a hybrid routing concept that combines client-side navigation with server-side integration into existing page structures. The focus is on integration into the main page and the feature editor, rather than dedicated municipality pages.

Current Routing Architecture

Main Page Integration

Municipalities are primarily displayed on the homepage (/) through the KommunenGrid component system:

src/pages/index.astro → /
  └── KommunenGrid component
      └── Client-side interaction

Feature Editor Integration

The feature editor uses municipality data for project-specific configuration:

src/pages/feature-editor/[featureId].astro → /feature-editor/{featureId}
  └── Municipality data for map projection

URL Structure

Current URLs

URLPurposeImplementation
/Homepage with municipalities gridindex.astro
/feature-editor/{featureId}Feature editor with municipality context[featureId].astro

Planned URLs (Potential Extension)

URLPurposeStatus
/municipalities/Overview of all municipalities🚧 Planned
/municipalities/{slug}Detail page of a municipality🚧 Planned
/municipalities/{slug}/mapMunicipality-specific map🚧 Planned

Dynamic Routes

Feature Editor Route

astro
---
// src/pages/feature-editor/[featureId].astro
import { getCollection } from "astro:content";

export async function getStaticPaths() {
  // Municipality-based routes could be generated here
  return [
    { params: { featureId: "example-1" } },
    { params: { featureId: "example-2" } }
  ];
}

// Municipality data is used for map configuration
try {
    const kommunen = await getCollection("kommunen");
    const kommune = kommunen.find((k) => k.data.wp_name === wp_name);

    if (kommune?.data.map?.projection) {
        targetProjection = kommune.data.map.projection;
    }
} catch (error) {
    console.warn("Could not load kommunen collection:", error);
}
---

Client-Side Navigation

KommunenGrid Interaction

The KommunenGrid component implements client-side navigation through event handling:

typescript
// Simplified representation of click handler
class KommunenClickHandler {
  bind() {
    document.addEventListener('click', (event) => {
      const target = event.target as HTMLElement;
      const kommuneCard = target.closest('.kommunen-card');
      
      if (kommuneCard) {
        const slug = kommuneCard.getAttribute('data-slug');
        const detail = kommuneCard.getAttribute('data-detail');
        
        this.handleKommuneClick(slug, JSON.parse(detail || '{}'));
      }
    });
  }

  private handleKommuneClick(slug: string, detail: any) {
    // Current implementation: Map navigation
    // Future: Navigation to municipality detail page
    this.navigateToKommune(slug, detail);
  }
}

Tab System on Homepage

The homepage implements a tab system for navigation between municipalities and categories:

javascript
// Tab switching on homepage
function switchTab(tabName: "kommunen" | "kategorien") {
  const kommunenGrid = document.getElementById("kommunen-grid");
  const kategorienGrid = document.getElementById("kategorien-grid");
  
  if (tabName === "kommunen") {
    kommunenGrid?.classList.remove("opacity-0", "absolute");
    kategorienGrid?.classList.add("opacity-0", "absolute");
  } else {
    kommunenGrid?.classList.add("opacity-0", "absolute");
    kategorienGrid?.classList.remove("opacity-0", "absolute");
  }
  
  // Persist tab state
  setPersistedTab(tabName);
}

Data Flow for Routing

Server-Side Data Preparation

astro
---
// Example for future municipality detail page
import { getCollection, getEntry } from 'astro:content';

export async function getStaticPaths() {
  const kommunen = await getCollection('kommunen');
  
  return kommunen.map(kommune => ({
    params: { 
      slug: kommune.slug 
    },
    props: { 
      kommune 
    }
  }));
}

const { slug } = Astro.params;
const { kommune } = Astro.props;
---

Props Passing

typescript
// Typing for route props
interface KommunePageProps {
  kommune: {
    id: string;
    slug: string;
    data: {
      title: string;
      wp_name: string;
      osmAdminLevels?: number[];
      map: {
        center: [number, number];
        zoom: number;
        projection: string;
      };
    };
    body: string;
  };
}
typescript
// Generation of links to municipality pages
function generateKommuneLinks(kommunen: KommuneData[]) {
  return kommunen.map(kommune => ({
    href: `/municipalities/${kommune.slug}`,
    label: kommune.title,
    description: `Details about ${kommune.title}`
  }));
}
typescript
// Breadcrumb structure for municipality pages
const breadcrumbs = [
  { href: '/', label: 'Home' },
  { href: '/municipalities', label: 'Municipalities' },
  { href: `/municipalities/${slug}`, label: title, current: true }
];

404 Handling

Missing Municipalities

astro
---
// Example for 404 handling in municipality routes
import { getEntry } from 'astro:content';

const { slug } = Astro.params;
const kommune = await getEntry('kommunen', slug);

if (!kommune) {
  return Astro.redirect('/404');
}
---

Fallback Routes

typescript
// Fallback for invalid slugs
export async function getStaticPaths() {
  const kommunen = await getCollection('kommunen');
  
  const paths = kommunen.map(kommune => ({
    params: { slug: kommune.slug }
  }));
  
  // Fallback for non-prerendered routes
  return {
    paths,
    fallback: 'blocking'
  };
}

URL Parameters and Query Strings

Dynamic Parameters

typescript
// Extraction of URL parameters
const { slug } = Astro.params;
const urlSearchParams = new URL(Astro.request.url).searchParams;
const adminLevel = urlSearchParams.get('adminLevel');
const view = urlSearchParams.get('view') || 'map';

Query String Handling

typescript
// Generation of URLs with query parameters
function generateKommuneUrl(slug: string, options: {
  adminLevel?: number;
  view?: string;
  highlight?: string;
}) {
  const params = new URLSearchParams();
  
  if (options.adminLevel) params.set('adminLevel', options.adminLevel.toString());
  if (options.view) params.set('view', options.view);
  if (options.highlight) params.set('highlight', options.highlight);
  
  return `/municipalities/${slug}?${params.toString()}`;
}

State Management via URLs

Map State

typescript
// Persistence of map state in URL
interface MapState {
  center: [number, number];
  zoom: number;
  layers: string[];
}

function serializeMapState(state: MapState): string {
  return btoa(JSON.stringify(state));
}

function deserializeMapState(encoded: string): MapState {
  return JSON.parse(atob(encoded));
}

Filter State

typescript
// URL-based filters
interface KommuneFilters {
  adminLevels: number[];
  hasOSMData: boolean;
  searchTerm: string;
}

function applyFiltersFromUrl(filters: KommuneFilters) {
  const url = new URL(window.location.href);
  url.searchParams.set('adminLevels', filters.adminLevels.join(','));
  url.searchParams.set('hasOSMData', filters.hasOSMData.toString());
  if (filters.searchTerm) {
    url.searchParams.set('search', filters.searchTerm);
  }
  
  window.history.pushState({}, '', url.toString());
}

Performance Optimizations

Lazy Loading

typescript
// Dynamic loading of municipality components
const KommuneDetail = await import('../components/KommuneDetail.astro');
const KommuneMap = await import('../components/KommuneMap.astro');

Prefetching

typescript
// Prefetching of municipality data
function prefetchKommuneData(slug: string) {
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = `/municipalities/${slug}`;
  link.as = 'document';
  document.head.appendChild(link);
}

Best Practices

URL Design

  1. Slug Consistency: Use consistent slugs based on filenames
  2. SEO-Friendly: Descriptive URLs for better search engine optimization
  3. Canonical URLs: Avoid duplicate content through correct canonical tags
  1. Breadcrumbs: Implement breadcrumb navigation for better UX
  2. Deep Linking: Enable direct links to specific views
  3. History Management: Proper browser history management

Error Handling

  1. 404 Pages: User-friendly 404 pages for non-existent municipalities
  2. Error Boundaries: Graceful degradation for errors
  3. Loading States: Appropriate loading indicators

Extension Possibilities

Internationalization

typescript
// Multilingual routes
export async function getStaticPaths() {
  const kommunen = await getCollection('kommunen');
  
  return kommunen.flatMap(kommune => [
    { params: { slug: kommune.slug }, props: { locale: 'de' } },
    { params: { slug: kommune.slug }, props: { locale: 'en' } }
  ]);
}

API Routes

typescript
// RESTful API for municipality data
// /api/municipalities/[slug].ts
export async function GET({ params }: { params: { slug: string } }) {
  const kommune = await getEntry('kommunen', params.slug);
  
  if (!kommune) {
    return new Response(null, { status: 404 });
  }
  
  return new Response(JSON.stringify(kommune), {
    headers: { 'Content-Type': 'application/json' }
  });
}

The current routing system provides a solid foundation for municipality integration and can be extended with dedicated municipality pages as needed.