Skip to content

Vue Integration Guide ​

Learn how to integrate route optimization and mapping into your Vue 3 applications using @route-optimization/vue.

Installation ​

bash
npm install @route-optimization/vue @route-optimization/core
# or
pnpm add @route-optimization/vue @route-optimization/core
# or
yarn add @route-optimization/vue @route-optimization/core

API Key Setup ​

Before using any composables or components, configure your Google Maps API key:

bash
# .env
VITE_GOOGLE_MAPS_API_KEY=your_api_key_here

Security

Never commit your API key to version control. Always use environment variables and add .env to your .gitignore file.

TIP

For production deployments, use your hosting platform's environment variable configuration (Vercel, Netlify, etc.). See Authentication Guide for best practices.

Core Concepts ​

The Vue package provides composables that integrate with Vue 3's Composition API:

  • useRouteMap - Main composable for map rendering and route management
  • useMapControls - Composable for map interaction controls
  • useRouteOptimization - Composable for route calculation and optimization
  • useOptimizationStatus - Composable for optimization progress tracking

Basic Map Rendering ​

Simple Route Map ​

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useRouteMap } from '@route-optimization/vue';

const mapRef = ref<HTMLDivElement>();

const { renderRoute, clearRoute, isReady } = useRouteMap({
  apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
  center: { lat: 13.7563, lng: 100.5018 }, // Bangkok
  zoom: 12,
  mapElement: mapRef,
});

const handleShowRoute = () => {
  renderRoute({
    stops: [
      {
        id: '1',
        location: { lat: 13.7563, lng: 100.5018 },
        name: 'Start Location',
      },
      {
        id: '2',
        location: { lat: 13.7467, lng: 100.5352 },
        name: 'End Location',
      },
    ],
  });
};
</script>

<template>
  <div>
    <div ref="mapRef" style="width: 100%; height: 600px" />

    <button @click="handleShowRoute" :disabled="!isReady">Show Route</button>
    <button @click="clearRoute" :disabled="!isReady">Clear Route</button>
  </div>
</template>

Route Map View Component ​

For simpler usage, use the pre-built RouteMapView component:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { RouteMapView } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';

const route = ref<Route | null>(null);

const handleError = (error: Error) => {
  console.error('Map error:', error);
};
</script>

<template>
  <RouteMapView
    api-key="YOUR_GOOGLE_MAPS_API_KEY"
    :route="route"
    :map-config="{
      center: { lat: 13.7563, lng: 100.5018 },
      zoom: 12,
    }"
    @error="handleError"
  />
</template>

Map Controls ​

Using Map Controls Composable ​

vue
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useRouteMap, useMapControls } from '@route-optimization/vue';

const mapRef = ref<HTMLDivElement>();

const { map, isReady } = useRouteMap({
  apiKey: 'YOUR_API_KEY',
  mapElement: mapRef,
});

const { center, zoom, bounds, setCenter, setZoom, fitBounds } = useMapControls(map);

const handleCenterBangkok = () => {
  setCenter({ lat: 13.7563, lng: 100.5018 });
};

const handleZoomIn = () => {
  setZoom((zoom.value ?? 12) + 1);
};

const currentCenter = computed(() => {
  if (!center.value) return 'Loading...';
  return `${center.value.lat.toFixed(4)}, ${center.value.lng.toFixed(4)}`;
});
</script>

<template>
  <div>
    <div ref="mapRef" style="width: 100%; height: 600px" />

    <div class="controls">
      <p>Center: {{ currentCenter }}</p>
      <p>Zoom: {{ zoom }}</p>

      <button @click="handleCenterBangkok">Center on Bangkok</button>
      <button @click="handleZoomIn">Zoom In</button>
    </div>
  </div>
</template>

Route Optimization ​

Basic Optimization ​

vue
<script setup lang="ts">
import { useRouteOptimization } from '@route-optimization/vue';
import type { OptimizationRequest } from '@route-optimization/core';

const { status, data, error, isLoading, isSuccess, optimize, reset } = useRouteOptimization({
  useMockMode: true, // Use mock mode for development
});

const handleOptimize = async () => {
  const request: OptimizationRequest = {
    shipments: [
      {
        id: 'shipment-1',
        deliveryLocation: { latitude: 13.7563, longitude: 100.5018 },
      },
      {
        id: 'shipment-2',
        deliveryLocation: { latitude: 13.7467, longitude: 100.5352 },
      },
    ],
    vehicles: [
      {
        id: 'vehicle-1',
        startLocation: { latitude: 13.7563, longitude: 100.5018 },
      },
    ],
  };

  try {
    const response = await optimize(request);
    console.log('Optimization result:', response);
  } catch (err) {
    console.error('Optimization failed:', err);
  }
};
</script>

<template>
  <div>
    <button @click="handleOptimize" :disabled="isLoading">
      {{ isLoading ? 'Optimizing...' : 'Optimize Routes' }}
    </button>

    <div v-if="isSuccess && data" class="result">
      <h3>Optimization Complete!</h3>
      <p>Routes: {{ data.routes?.length ?? 0 }}</p>
      <p>Total Distance: {{ data.metrics?.totalDistance ?? 0 }} km</p>
    </div>

    <div v-if="error" class="error">Error: {{ error }}</div>
  </div>
</template>

Production Mode with Google Cloud ​

vue
<script setup lang="ts">
import { useRouteOptimization } from '@route-optimization/vue';

const { optimize } = useRouteOptimization({
  useMockMode: false,
  projectId: 'your-google-cloud-project',
  credentials: {
    client_email: 'service-account@project.iam.gserviceaccount.com',
    private_key: import.meta.env.VITE_GOOGLE_PRIVATE_KEY,
  },
});
</script>

Progress Tracking ​

vue
<script setup lang="ts">
import { useRouteOptimization, useOptimizationStatus } from '@route-optimization/vue';

const { optimize } = useRouteOptimization({ useMockMode: true });

const {
  progress,
  step,
  setProgress,
  reset: resetProgress,
} = useOptimizationStatus({
  onStart: () => console.log('Optimization started'),
  onSuccess: () => console.log('Optimization complete!'),
  onError: (error) => console.error('Failed:', error),
});

const handleOptimize = async () => {
  resetProgress();
  setProgress(0, 'Initializing...');

  // Simulate progress
  setProgress(25, 'Calculating distances...');

  const response = await optimize(request);

  setProgress(75, 'Optimizing routes...');

  if (response.success) {
    setProgress(100, 'Complete!');
  }
};
</script>

<template>
  <div>
    <button @click="handleOptimize">Optimize</button>

    <div class="progress">
      <div class="progress-bar" :style="{ width: `${progress}%` }" />
      <p>{{ step }}</p>
    </div>
  </div>
</template>

<style scoped>
.progress {
  width: 100%;
  background: #f0f0f0;
  border-radius: 4px;
  overflow: hidden;
}

.progress-bar {
  height: 24px;
  background: #4caf50;
  transition: width 0.3s ease;
}
</style>

Complete Example ​

Combining map rendering with route optimization:

vue
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useRouteOptimization, RouteMapView } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';

const route = ref<Route | null>(null);
const locations = reactive([
  { lat: 13.7563, lng: 100.5018, name: 'Bangkok' },
  { lat: 13.7467, lng: 100.5352, name: 'Chatuchak' },
  { lat: 13.7307, lng: 100.5418, name: 'Victory Monument' },
]);

const { optimize, isLoading, isSuccess } = useRouteOptimization({
  useMockMode: true,
});

const handleOptimize = async () => {
  const request = {
    shipments: locations.map((loc, i) => ({
      id: `shipment-${i}`,
      deliveryLocation: { latitude: loc.lat, longitude: loc.lng },
    })),
    vehicles: [
      {
        id: 'vehicle-1',
        startLocation: {
          latitude: locations[0].lat,
          longitude: locations[0].lng,
        },
      },
    ],
  };

  const response = await optimize(request);

  if (response.success && response.routes?.[0]) {
    // Convert optimization result to Route format
    route.value = {
      stops:
        response.routes[0].stops?.map((stop, i) => ({
          id: stop.shipmentId || `stop-${i}`,
          location: {
            lat: stop.location?.latitude ?? 0,
            lng: stop.location?.longitude ?? 0,
          },
          name: locations[i]?.name ?? `Stop ${i + 1}`,
        })) ?? [],
    };
  }
};
</script>

<template>
  <div class="app">
    <div class="sidebar">
      <h2>Route Optimization</h2>

      <div class="locations">
        <div v-for="(loc, i) in locations" :key="i" class="location-item">
          {{ loc.name }}
        </div>
      </div>

      <button @click="handleOptimize" :disabled="isLoading">
        {{ isLoading ? 'Optimizing...' : 'Optimize Route' }}
      </button>

      <div v-if="isSuccess" class="success">✓ Route optimized successfully!</div>
    </div>

    <div class="map-container">
      <RouteMapView
        :api-key="import.meta.env.VITE_GOOGLE_MAPS_API_KEY"
        :route="route"
        :map-config="{
          center: { lat: 13.7563, lng: 100.5018 },
          zoom: 12,
        }"
      />
    </div>
  </div>
</template>

<style scoped>
.app {
  display: flex;
  height: 100vh;
}

.sidebar {
  width: 300px;
  padding: 20px;
  background: #f5f5f5;
}

.map-container {
  flex: 1;
}

.locations {
  margin: 20px 0;
}

.location-item {
  padding: 10px;
  margin: 5px 0;
  background: white;
  border-radius: 4px;
}

button {
  width: 100%;
  padding: 12px;
  background: #4caf50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.success {
  margin-top: 10px;
  padding: 10px;
  background: #e8f5e9;
  color: #2e7d32;
  border-radius: 4px;
}
</style>

TypeScript Support ​

All composables are fully typed. Import types from the packages:

ts
import type {
  Route,
  Stop,
  MapConfig,
  OptimizationRequest,
  OptimizationResponse,
} from '@route-optimization/core';

import type {
  UseRouteMapOptions,
  UseRouteMapReturn,
  UseMapControlsReturn,
  UseRouteOptimizationResult,
} from '@route-optimization/vue';

Composition API Patterns ​

Composable Reusability ​

Create custom composables for common patterns:

ts
// composables/useOptimizedRoute.ts
import { ref } from 'vue';
import { useRouteOptimization } from '@route-optimization/vue';
import type { Route } from '@route-optimization/core';

export function useOptimizedRoute() {
  const route = ref<Route | null>(null);
  const { optimize, isLoading, isSuccess } = useRouteOptimization({
    useMockMode: true,
  });

  const optimizeLocations = async (locations: Array<{ lat: number; lng: number }>) => {
    const request = {
      shipments: locations.map((loc, i) => ({
        id: `shipment-${i}`,
        deliveryLocation: { latitude: loc.lat, longitude: loc.lng },
      })),
      vehicles: [
        {
          id: 'vehicle-1',
          startLocation: { latitude: locations[0].lat, longitude: locations[0].lng },
        },
      ],
    };

    const response = await optimize(request);

    if (response.success && response.routes?.[0]) {
      route.value = {
        stops:
          response.routes[0].stops?.map((stop, i) => ({
            id: stop.shipmentId || `stop-${i}`,
            location: {
              lat: stop.location?.latitude ?? 0,
              lng: stop.location?.longitude ?? 0,
            },
            name: `Stop ${i + 1}`,
          })) ?? [],
      };
    }

    return route.value;
  };

  return {
    route,
    optimizeLocations,
    isLoading,
    isSuccess,
  };
}

Reactive State Management ​

Use Vue's reactivity system with the composables:

vue
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { useRouteMap } from '@route-optimization/vue';

const selectedRoute = ref<Route | null>(null);
const mapRef = ref<HTMLDivElement>();

const { renderRoute, isReady } = useRouteMap({
  apiKey: 'YOUR_API_KEY',
  mapElement: mapRef,
});

// Automatically render when route changes
watch(selectedRoute, (newRoute) => {
  if (newRoute && isReady.value) {
    renderRoute(newRoute);
  }
});

const routeStats = computed(() => {
  if (!selectedRoute.value) return null;

  return {
    stops: selectedRoute.value.stops.length,
    // Add more computed stats
  };
});
</script>

Best Practices ​

1. Environment Variables ​

Store API keys in environment variables:

env
# .env.local
VITE_GOOGLE_MAPS_API_KEY=your_api_key
VITE_GOOGLE_PROJECT_ID=your_project_id

Access in your app:

ts
const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY;

2. Error Handling ​

Use try-catch with proper error handling:

vue
<script setup lang="ts">
import { ref } from 'vue';

const error = ref<string | null>(null);

const handleOptimize = async () => {
  error.value = null;

  try {
    const result = await optimize(request);
    // Handle success
  } catch (err) {
    error.value = err instanceof Error ? err.message : 'Unknown error';
  }
};
</script>

<template>
  <div v-if="error" class="error">
    {{ error }}
  </div>
</template>

3. Cleanup ​

Components automatically clean up, but you can use onUnmounted:

ts
import { onUnmounted } from 'vue';

onUnmounted(() => {
  clearRoute();
  reset();
});

4. Computed Properties ​

Use computed for derived state:

ts
const routeDistance = computed(() => {
  if (!data.value?.metrics) return 0;
  return data.value.metrics.totalDistance;
});

Nuxt 3 Integration ​

For Nuxt 3 applications, use client-only rendering for map components:

vue
<template>
  <ClientOnly>
    <RouteMapView :api-key="apiKey" :route="route" />
  </ClientOnly>
</template>

Next Steps ​

Released under the MIT License.