Nuxt 3 brings the best of Vue's Composition API together with universal rendering, auto-imports, and a zero-config production server. For astrology app developers, this combination means SEO-friendly birth chart pages, reactive horoscope components, and a clean pattern for keeping API keys server-side — all without boilerplate.
In this tutorial, we'll build a production-ready astrology app using Nuxt 3 and Vedika API — the only B2B astrology API with AI-powered conversational capabilities, 140+ endpoints, and Swiss Ephemeris precision. You'll implement composables for API calls, Pinia for birth data state, server/api/ routes for key security, and useHead for dynamic SEO.
What You'll Build: A Nuxt 3 astrology app with server-rendered birth chart and horoscope pages, a Pinia store for birth data, composables that wrap Vedika API calls, a secure server/api/ proxy, and an AI chatbot component with real-time responses.
Why Nuxt 3 for Astrology Apps?
Universal Rendering
SSR, SSG, and SPA modes in one project. Birth chart pages SSR for SEO; chat interface runs as SPA for real-time updates.
Auto-Imports
Composables, components, and Pinia stores are auto-imported. No import statements for useBirthStore() or useVedika().
Nitro Server
Built-in server/api/ routes run in Node.js or edge environments. Proxy Vedika API with your key hidden from the client.
useHead for SEO
Dynamic title, description, and Open Graph tags per page. Birth chart pages get unique meta tags with the user's ascendant sign.
Prerequisites
- Node.js 20+ installed
- Familiarity with Vue 3 Composition API
- Vedika API key — get yours here
Step 1: Create the Nuxt 3 Project
npx nuxi@latest init astrology-app
cd astrology-app
npm install @pinia/nuxt pinia
npm install @nuxt/ui
Configure nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@pinia/nuxt',
'@nuxt/ui'
],
runtimeConfig: {
vedikaApiKey: process.env.VEDIKA_API_KEY,
public: {
vedikaBaseUrl: 'https://api.vedika.io'
}
},
nitro: {
preset: 'node-server'
}
});
Create a .env file:
VEDIKA_API_KEY=vk_live_your_key_here
Step 2: Server API Routes (Secure Proxy)
Nuxt 3 server routes run on Nitro and have access to private runtime config. Create routes that proxy Vedika API calls:
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const body = await readBody(event);
const { datetime, latitude, longitude, timezone = '+05:30' } = body;
if (!datetime || latitude == null || longitude == null) {
throw createError({ statusCode: 400, message: 'Missing birth details' });
}
const res = await $fetch('https://api.vedika.io/v2/astrology/birth-chart', {
method: 'POST',
headers: {
'X-API-Key': config.vedikaApiKey,
'Content-Type': 'application/json'
},
body: { datetime, latitude, longitude, timezone }
});
return res;
});
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const { sign, language = 'en' } = await readBody(event);
return $fetch('https://api.vedika.io/v2/astrology/horoscope/daily', {
method: 'POST',
headers: { 'X-API-Key': config.vedikaApiKey },
body: { sign, language }
});
});
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig();
const { question, birth, language = 'en' } = await readBody(event);
return $fetch('https://api.vedika.io/api/v1/astrology/query', {
method: 'POST',
headers: { 'X-API-Key': config.vedikaApiKey },
body: {
question,
language,
birthDetails: {
datetime: birth.datetime,
latitude: birth.latitude,
longitude: birth.longitude,
timezone: birth.timezone || '+05:30'
}
}
});
});
Step 3: Pinia Store for Birth Data
Create a Pinia store to hold birth details across all pages. In Nuxt 3, this is auto-imported everywhere:
import { defineStore } from 'pinia';
export interface BirthDetails {
datetime: string;
latitude: number;
longitude: number;
timezone: string;
locationName: string;
}
export const useBirthStore = defineStore('birth', {
state: (): { data: BirthDetails | null; chart: any | null } => ({
data: null,
chart: null
}),
getters: {
isReady: (state) =>
!!state.data?.datetime &&
state.data?.latitude != null &&
state.data?.longitude != null,
ascendant: (state) => state.chart?.data?.ascendant?.sign || null,
planets: (state) => state.chart?.data?.planets || []
},
actions: {
async setBirthAndFetchChart(details: BirthDetails) {
this.data = details;
this.chart = await $fetch('/api/birth-chart', {
method: 'POST',
body: details
});
},
clear() {
this.data = null;
this.chart = null;
}
},
persist: true
});
Step 4: Composables for Vedika API
Auto-imported composables wrap API calls in clean, reusable functions:
export function useVedika() {
const birthStore = useBirthStore();
async function getDailyHoroscope(sign: string, language = 'en') {
return $fetch('/api/horoscope', {
method: 'POST',
body: { sign, language }
});
}
async function askQuestion(question: string, language = 'en') {
if (!birthStore.isReady) throw new Error('No birth details');
return $fetch('/api/chat', {
method: 'POST',
body: { question, birth: birthStore.data, language }
});
}
async function getKundliMatch(birth1: any, birth2: any) {
return $fetch('/api/kundli-match', {
method: 'POST',
body: { birth1, birth2 }
});
}
return { getDailyHoroscope, askQuestion, getKundliMatch };
}
Step 5: Birth Chart Page with useAsyncData
The birth chart page uses useAsyncData for server-side data fetching with a loading state:
<script setup lang="ts">
const birthStore = useBirthStore();
const router = useRouter();
const form = reactive({
datetime: '',
latitude: null as number | null,
longitude: null as number | null,
timezone: '+05:30',
locationName: ''
});
const submitting = ref(false);
const error = ref('');
async function submitChart() {
if (!form.datetime || form.latitude == null || form.longitude == null) {
error.value = 'Please fill all birth details';
return;
}
submitting.value = true;
error.value = '';
try {
await birthStore.setBirthAndFetchChart({ ...form } as any);
} catch (e: any) {
error.value = e.message || 'Failed to fetch birth chart';
} finally {
submitting.value = false;
}
}
useHead({
title: computed(() =>
birthStore.ascendant
? `${birthStore.ascendant} Ascendant Birth Chart | Vedika`
: 'Vedic Birth Chart Calculator | Vedika'
),
meta: [
{
name: 'description',
content: computed(() =>
birthStore.ascendant
? `Your Vedic birth chart with ${birthStore.ascendant} rising. Computed with Swiss Ephemeris precision.`
: 'Generate your Vedic birth chart with astronomical precision.'
)
}
]
});
</script>
<template>
<div class="chart-page">
<h1>Vedic Birth Chart Calculator</h1>
<form @submit.prevent="submitChart">
<div class="form-group">
<label>Date & Time of Birth</label>
<input v-model="form.datetime" type="datetime-local" required />
</div>
<div class="form-group">
<label>Latitude</label>
<input v-model.number="form.latitude" type="number" step="0.0001"
placeholder="28.6139 (New Delhi)" required />
</div>
<div class="form-group">
<label>Longitude</label>
<input v-model.number="form.longitude" type="number" step="0.0001"
placeholder="77.2090" required />
</div>
<div class="form-group">
<label>Timezone (UTC offset)</label>
<input v-model="form.timezone" type="text" placeholder="+05:30" />
</div>
<p v-if="error" class="error">{{ error }}</p>
<button type="submit" :disabled="submitting">
{{ submitting ? 'Calculating...' : 'Generate Birth Chart' }}
</button>
</form>
<div v-if="birthStore.chart" class="chart-result">
<h2>Ascendant: {{ birthStore.ascendant }}</h2>
<table>
<thead>
<tr><th>Planet</th><th>Sign</th><th>House</th><th>Degree</th></tr>
</thead>
<tbody>
<tr v-for="planet in birthStore.planets" :key="planet.name">
<td>{{ planet.name }}</td>
<td>{{ planet.sign }}</td>
<td>{{ planet.house }}</td>
<td>{{ planet.degree?.toFixed(2) }}°</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
Step 6: Daily Horoscope Page with useFetch
useFetch is the idiomatic Nuxt 3 pattern for data that's fetched on page load and needs SSR:
<script setup lang="ts">
const route = useRoute();
const sign = computed(() => route.params.sign as string);
const { data: horoscope, pending, error, refresh } = await useFetch(
'/api/horoscope',
{
method: 'POST',
body: computed(() => ({ sign: sign.value, language: 'en' })),
watch: [sign]
}
);
useHead({
title: computed(() => `${capitalize(sign.value)} Daily Horoscope 2026 | Vedika`),
meta: [
{
name: 'description',
content: computed(() =>
`Today's ${capitalize(sign.value)} horoscope powered by Vedika AI. ` +
'Swiss Ephemeris precision, updated daily.'
)
},
{ property: 'og:type', content: 'article' }
]
});
function capitalize(s: string) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
</script>
<template>
<article>
<h1>{{ capitalize(sign) }} Daily Horoscope</h1>
<p v-if="pending">Loading today's prediction...</p>
<p v-else-if="error">Failed to load horoscope.</p>
<div v-else-if="horoscope" class="horoscope-content">
<p>{{ horoscope.data?.prediction || horoscope.data?.horoscope }}</p>
<button @click="refresh">Refresh</button>
</div>
</article>
</template>
Ready to Build Your Nuxt Astrology App?
Try the FREE Sandbox for development — 65 mock endpoints, no credit card required. Production plans from $12/month.
Get Your API Key
Step 7: AI Chatbot Component
A reactive Vue component for the AI chatbot that reads birth data from the Pinia store:
<script setup lang="ts">
const birthStore = useBirthStore();
const { askQuestion } = useVedika();
interface Message { role: 'user' | 'ai'; text: string }
const messages = ref<Message[]>([]);
const input = ref('');
const loading = ref(false);
async function sendMessage() {
if (!input.value.trim() || loading.value || !birthStore.isReady) return;
const question = input.value;
input.value = '';
messages.value.push({ role: 'user', text: question });
loading.value = true;
try {
const result: any = await askQuestion(question);
const answer = result?.data?.answer || result?.answer || 'No response';
messages.value.push({ role: 'ai', text: answer });
} catch (e: any) {
messages.value.push({ role: 'ai', text: `Error: ${e.message}` });
} finally {
loading.value = false;
}
}
</script>
<template>
<div class="chat-container">
<div v-if="!birthStore.isReady" class="no-birth">
<NuxtLink to="/chart">Enter birth details to chat with Vedika AI</NuxtLink>
</div>
<template v-else>
<div class="messages">
<div v-for="(msg, i) in messages" :key="i"
:class="['message', msg.role]">{{ msg.text }}</div>
<div v-if="loading" class="message ai thinking">
Vedika AI is thinking...
</div>
</div>
<form @submit.prevent="sendMessage" class="chat-form">
<input
v-model="input"
type="text"
placeholder="Ask about your birth chart..."
:disabled="loading"
/>
<button type="submit" :disabled="loading">Send</button>
</form>
</template>
</div>
</template>
Step 8: Multi-Language Support
Vedika API responds in 30 languages. Detect the user's locale in Nuxt and pass it to the API:
export function useLocale() {
const supportedLangs = [
'en', 'hi', 'ta', 'te', 'bn', 'gu',
'mr', 'kn', 'ml', 'pa', 'ar', 'fr'
];
const detectedLang = computed(() => {
if (process.server) return 'en';
const browserLang = navigator.language.split('-')[0];
return supportedLangs.includes(browserLang) ? browserLang : 'en';
});
return { detectedLang };
}
Production Deployment
npm run build
node .output/server/index.mjs
npx vercel
gcloud run deploy astrology-nuxt \
--source . \
--set-env-vars VEDIKA_API_KEY=vk_live_xxx \
--region asia-south1 \
--allow-unauthenticated
npm run generate
Why Choose Vedika API?
AI-Powered
The only astrology API with a built-in AI engine. Natural language questions — no complex calculation code.
140+ Endpoints
Birth charts, yogas, doshas, dashas, Ashtakavarga, transits, Kundali matching, numerology, and more.
30 Languages
Hindi, Tamil, Telugu, Bengali, Gujarati, Marathi, Kannada, Arabic, and 22 more languages supported.
Swiss Ephemeris
Astronomical-grade planetary accuracy. The same precision used by professional astrologers and research applications.
MCP Server
World's first astrology MCP server. AI agents and automation tools can call Vedika natively.
From $12/month
Starter covers indie developers. Business plan at $120/month for production Nuxt apps with traffic.
Pricing
Vedika API offers transparent pricing starting at $12/month:
- Starter Plan: $12/month — Perfect for Nuxt prototypes and personal projects
- Professional Plan: $60/month — For growing apps with regular traffic
- Business Plan: $120/month — For production apps serving real users
- Enterprise Plan: $240/month — Unlimited usage with add-on credits
All plans include 140+ endpoints, AI chatbot, and 30-language support. View detailed pricing
Conclusion
Nuxt 3 and Vedika API together deliver a production-ready astrology app with minimal boilerplate. Nuxt handles SSR, SEO, and the full-stack architecture — Vedika handles Swiss Ephemeris calculations, AI predictions, and 30 languages.
- server/api/ routes: Vedika API key never leaves the server
- Pinia stores: Birth data persists across all pages automatically
- useFetch: SSR-ready data fetching for Google-indexable horoscope pages
- useHead: Dynamic per-page SEO meta tags with computed values
- Auto-imports: Composables and stores used without any import statements
Next steps:
- Get your Vedika API key at vedika.io/signup
- Read the full API documentation
- Try the free sandbox at vedika.io/sandbox
- Add Kundali matching and transit predictions to your app
About Vedika Intelligence: Vedika is the only B2B astrology API with AI-powered chatbot capabilities, serving production apps worldwide. Our AI engine enables natural language astrology queries with Swiss Ephemeris verified precision, supporting both Vedic and Western astrology systems across 30 languages.