Vue Integration Guide
Learn how to integrate route optimization and mapping into your Vue 3 applications using @route-optimization/vue.
Installation
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/coreAPI Key Setup
Before using any composables or components, configure your Google Maps API key:
# .env
VITE_GOOGLE_MAPS_API_KEY=your_api_key_hereSecurity
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 managementuseMapControls- Composable for map interaction controlsuseRouteOptimization- Composable for route calculation and optimizationuseOptimizationStatus- Composable for optimization progress tracking
Basic Map Rendering
Simple Route Map
<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:
<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
<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
<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
<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
<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:
<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:
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:
// 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:
<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.local
VITE_GOOGLE_MAPS_API_KEY=your_api_key
VITE_GOOGLE_PROJECT_ID=your_project_idAccess in your app:
const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY;2. Error Handling
Use try-catch with proper error handling:
<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:
import { onUnmounted } from 'vue';
onUnmounted(() => {
clearRoute();
reset();
});4. Computed Properties
Use computed for derived state:
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:
<template>
<ClientOnly>
<RouteMapView :api-key="apiKey" :route="route" />
</ClientOnly>
</template>Next Steps
- Learn about Vanilla JS Integration
- Explore Route Calculation in depth
- Check out API Reference