Build an Astrology App with SvelteKit & Vedika API

Build an Astrology App with SvelteKit and Vedika API
Published: March 13, 2026 | By Vedika Intelligence | Reading time: 12 minutes

SvelteKit is fast, SEO-friendly, and ships no runtime overhead to the browser. For astrology app developers who need Google-indexable birth chart pages and horoscope content, SvelteKit's server-side rendering and built-in form actions make it the most productive framework available in 2026.

In this tutorial, we'll build a production-ready astrology app using SvelteKit and Vedika API — the only B2B astrology API with AI-powered conversational capabilities, 140+ endpoints, and Swiss Ephemeris astronomical precision. By the end, you'll have a fully server-rendered app with birth chart pages, reactive horoscope components, form actions, and an AI chatbot.

What You'll Build: A SvelteKit astrology app with server-side birth chart loading via +page.server.ts, reactive Svelte stores for birth data, form actions for user input, and an AI chat route powered by Vedika API. All API keys stay on the server — never exposed to the browser.

Why SvelteKit for Astrology Apps?

No Virtual DOM

Svelte compiles to vanilla JavaScript. No runtime framework overhead means faster Time to Interactive for your users.

Built-in SSR

+page.server.ts load functions run on the server. Birth chart pages are pre-rendered and Google-indexable by default.

Form Actions

Native progressive enhancement. Birth detail forms work without JavaScript, with full enhancement when JS loads.

Reactive Stores

$: reactive declarations and writable stores make birth data sync across components without prop drilling.

Prerequisites

Step 1: Create the SvelteKit Project

Scaffold a new SvelteKit project with TypeScript:

# Create SvelteKit project npm create svelte@latest astrology-app # Select: Skeleton project, TypeScript, ESLint, Prettier cd astrology-app npm install # Add fetch utility (SvelteKit uses native fetch, no extra dep needed) npm install -D @sveltejs/adapter-node

Configure Adapter and Environment

Update svelte.config.js to use the Node adapter for production deployment:

// svelte.config.js import adapter from '@sveltejs/adapter-node'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; const config = { preprocess: vitePreprocess(), kit: { adapter: adapter() } }; export default config;

Create a .env file at the project root — SvelteKit keeps these private automatically:

# .env — NEVER commit this file VEDIKA_API_KEY=vk_live_your_key_here

Step 2: Create the Vedika API Client (Server-Only)

Create a server-side utility that all load functions and API routes will share:

// src/lib/server/vedika.ts import { VEDIKA_API_KEY } from '$env/static/private'; const BASE = 'https://api.vedika.io'; export interface BirthDetails { datetime: string; // ISO format: "1995-08-15T10:30:00" latitude: number; longitude: number; timezone: string; // UTC offset: "+05:30" } async function vedikaFetch(path: string, body: unknown) { const res = await fetch(`${BASE}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': VEDIKA_API_KEY }, body: JSON.stringify(body) }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.message || `Vedika API error ${res.status}`); } return res.json(); } export async function getBirthChart(birth: BirthDetails) { return vedikaFetch('/v2/astrology/birth-chart', { datetime: birth.datetime, latitude: birth.latitude, longitude: birth.longitude, timezone: birth.timezone }); } export async function getDailyHoroscope(sign: string, lang = 'en') { return vedikaFetch('/v2/astrology/horoscope/daily', { sign, language: lang }); } export async function askVedika(question: string, birth: BirthDetails, language = 'en') { return vedikaFetch('/api/v1/astrology/query', { question, language, birthDetails: { datetime: birth.datetime, latitude: birth.latitude, longitude: birth.longitude, timezone: birth.timezone } }); }

Step 3: Birth Detail Form with SvelteKit Form Actions

Form actions let users submit birth details — and the form works even without JavaScript thanks to progressive enhancement:

// src/routes/chart/+page.server.ts import { fail } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; import { getBirthChart } from '$lib/server/vedika'; export const load: PageServerLoad = async ({ url }) => { // Pre-load chart if query params are present (shareable URLs) const dt = url.searchParams.get('dt'); const lat = url.searchParams.get('lat'); const lng = url.searchParams.get('lng'); const tz = url.searchParams.get('tz') || '+05:30'; if (dt && lat && lng) { const chart = await getBirthChart({ datetime: dt, latitude: parseFloat(lat), longitude: parseFloat(lng), timezone: tz }); return { chart }; } return { chart: null }; }; export const actions: Actions = { default: async ({ request }) => { const form = await request.formData(); const datetime = form.get('datetime') as string; const latitude = parseFloat(form.get('latitude') as string); const longitude = parseFloat(form.get('longitude') as string); const timezone = form.get('timezone') as string || '+05:30'; if (!datetime || isNaN(latitude) || isNaN(longitude)) { return fail(400, { error: 'Please fill all birth details' }); } try { const chart = await getBirthChart({ datetime, latitude, longitude, timezone }); return { success: true, chart }; } catch (e: any) { return fail(500, { error: e.message }); } } };

Birth Chart Svelte Component

<!-- src/routes/chart/+page.svelte --> <script lang="ts"> import { enhance } from '$app/forms'; import type { PageData, ActionData } from './$types'; export let data: PageData; export let form: ActionData; $: chart = form?.chart || data.chart; $: planets = chart?.data?.planets || []; $: ascendant = chart?.data?.ascendant; </script> <svelte:head> <title>Birth Chart Calculator | Vedika Astrology</title> <meta name="description" content="Generate your Vedic birth chart with Swiss Ephemeris precision." /> </svelte:head> <main class="container"> <h1>Your Vedic Birth Chart</h1> <form method="POST" use:enhance> <label> Date & Time of Birth <input type="datetime-local" name="datetime" required /> </label> <label> Latitude <input type="number" name="latitude" step="0.0001" placeholder="28.6139" required /> </label> <label> Longitude <input type="number" name="longitude" step="0.0001" placeholder="77.2090" required /> </label> <label> Timezone (UTC offset) <input type="text" name="timezone" value="+05:30" /> </label> <button type="submit">Generate Chart</button> </form> {#if form?.error} <p class="error">{form.error}</p> {/if} {#if chart} <section class="chart-result"> <h2>Ascendant: {ascendant?.sign}</h2> <table> <thead> <tr> <th>Planet</th><th>Sign</th><th>House</th><th>Degree</th><th>Status</th> </tr> </thead> <tbody> {#each planets as p} <tr> <td>{p.name}</td> <td>{p.sign}</td> <td>{p.house}</td> <td>{p.degree?.toFixed(2)}°</td> <td>{p.isRetrograde ? 'Retrograde' : 'Direct'}</td> </tr> {/each} </tbody> </table> </section> {/if} </main>

Step 4: Svelte Stores for Birth Data

A writable store lets any component across the app access and update the user's birth details reactively:

// src/lib/stores/birthData.ts import { writable, derived } from 'svelte/store'; export interface BirthData { datetime: string; latitude: number; longitude: number; timezone: string; locationName: string; } function createBirthStore() { const { subscribe, set, update } = writable<BirthData | null>(null); return { subscribe, setData(data: BirthData) { set(data); // Persist to sessionStorage for page refreshes if (typeof window !== 'undefined') { sessionStorage.setItem('vedika_birth', JSON.stringify(data)); } }, clear() { set(null); } }; } export const birthData = createBirthStore(); // Derived store: true when birth details are complete export const birthReady = derived( birthData, ($b) => !!$b?.datetime && !!$b?.latitude && !!$b?.longitude );

Using the Store in Components

<!-- src/lib/components/HoroscopeWidget.svelte --> <script lang="ts"> import { birthData, birthReady } from '$lib/stores/birthData'; let prediction = ''; let loading = false; // Reactive: refetch whenever birthData changes $: if ($birthReady && $birthData) { fetchPrediction($birthData); } async function fetchPrediction(birth: typeof $birthData) { loading = true; const res = await fetch('/api/horoscope', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ birth }) }); const data = await res.json(); prediction = data.prediction || data.horoscope || ''; loading = false; } </script> {#if $birthReady} {#if loading} <p>Loading your prediction...</p> {:else if prediction} <div class="prediction"> <h3>Your Daily Prediction</h3> <p>{prediction}</p> </div> {/if} {:else} <p>Enter your birth details to see your daily prediction.</p> {/if}

Step 5: Server API Route for Horoscope

Create a SvelteKit server route that proxies requests to Vedika — keeping the API key server-side:

// src/routes/api/horoscope/+server.ts import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { getDailyHoroscope } from '$lib/server/vedika'; export const POST: RequestHandler = async ({ request }) => { const { sign, language } = await request.json(); if (!sign) { return json({ error: 'sign is required' }, { status: 400 }); } const data = await getDailyHoroscope(sign, language || 'en'); return json(data); };

Step 6: AI Chat Route with Streaming

Create an API route that streams Vedika's AI responses for a real-time chat experience:

// src/routes/api/chat/+server.ts import type { RequestHandler } from './$types'; import { VEDIKA_API_KEY } from '$env/static/private'; export const POST: RequestHandler = async ({ request }) => { const { question, birth, language } = await request.json(); // Stream from Vedika's SSE endpoint const upstream = await fetch('https://api.vedika.io/api/v1/astrology/query/stream', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': VEDIKA_API_KEY }, body: JSON.stringify({ question, language: language || 'en', birthDetails: { datetime: birth.datetime, latitude: birth.latitude, longitude: birth.longitude, timezone: birth.timezone || '+05:30' } }) }); // Forward the SSE stream directly to the browser return new Response(upstream.body, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache' } }); };

Chat Component Consuming the SSE Stream

<!-- src/lib/components/AstroChat.svelte --> <script lang="ts"> import { birthData } from '$lib/stores/birthData'; let messages: { role: 'user' | 'ai'; text: string }[] = []; let input = ''; let streaming = false; async function send() { if (!input.trim() || streaming || !$birthData) return; const question = input; input = ''; messages = [...messages, { role: 'user', text: question }]; messages = [...messages, { role: 'ai', text: '' }]; streaming = true; const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, birth: $birthData }) }); const reader = res.body!.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n').filter(l => l.startsWith('data: ')); for (const line of lines) { const text = line.slice(6); if (text === '[DONE]') break; try { const { token } = JSON.parse(text); if (token) { messages[messages.length - 1].text += token; messages = [...messages]; // trigger reactivity } } catch {} } } streaming = false; } </script> <div class="chat"> <div class="messages"> {#each messages as m} <div class="message {m.role}">{m.text}</div> {/each} {#if streaming} <div class="typing-indicator">Vedika AI is thinking...</div> {/if} </div> <form on:submit|preventDefault={send}> <input bind:value={input} placeholder="Ask about your birth chart..." /> <button type="submit" disabled={streaming}>Send</button> </form> </div>

Ready to Build Your SvelteKit 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: Layout and SEO with <svelte:head>

SvelteKit makes per-page SEO trivial. Add dynamic meta tags based on server-loaded chart data:

<!-- src/routes/chart/+layout.svelte --> <script lang="ts"> import type { LayoutData } from './$types'; export let data: LayoutData; </script> <svelte:head> <title>{data.pageTitle || 'Vedic Birth Chart'} | Vedika Astrology</title> <meta name="description" content={data.pageDescription || 'Generate your Vedic birth chart with Swiss Ephemeris precision.'} /> <link rel="canonical" href="https://vedika.io/chart" /> </svelte:head> <slot />

Step 8: Multi-Language Support

Vedika API responds in 30 languages. Detect the browser locale from the Accept-Language header in your server load function:

// src/routes/horoscope/+page.server.ts import type { PageServerLoad } from './$types'; import { getDailyHoroscope } from '$lib/server/vedika'; export const load: PageServerLoad = async ({ request, url }) => { const sign = url.searchParams.get('sign') || 'aries'; // Auto-detect language from browser const acceptLang = request.headers.get('accept-language') || 'en'; const lang = acceptLang.split(',')[0].split('-')[0]; // "hi-IN" → "hi" const horoscope = await getDailyHoroscope(sign, lang); return { horoscope, sign, lang }; };

Production Deployment

Deploy the SvelteKit app to any Node.js host — Fly.io, Railway, or Google Cloud Run:

# Build for production npm run build # Test production build locally node build # Deploy to Fly.io fly launch fly deploy # Deploy to Google Cloud Run gcloud run deploy astrology-app \ --source . \ --set-env-vars VEDIKA_API_KEY=vk_live_xxx \ --region asia-south1 \ --allow-unauthenticated

Why Choose Vedika API?

AI-Powered

The only astrology API with a built-in AI engine. Ask questions in plain English — no manual calculation logic.

140+ Endpoints

Birth charts, planetary positions, yogas, doshas, dashas, transits, Ashtakavarga, numerology, and more.

30 Languages

Hindi, Tamil, Telugu, Bengali, Gujarati, Marathi, Kannada, Arabic, and 22 more. Auto-detected from headers.

Swiss Ephemeris

Astronomical-grade planetary position accuracy — the same library used by professional astrologers worldwide.

MCP Server

World's first astrology MCP server. AI agents can call Vedika natively for automated predictions.

From $12/month

Starter plan covers indie developers. Scale to Business at $120/month for production traffic.

Pricing

Vedika API offers transparent pricing starting at just $12/month:

All plans include the full API with 140+ endpoints, AI chatbot, and 30-language support. View detailed pricing

Conclusion

SvelteKit and Vedika API are a natural pairing for production astrology apps. SvelteKit handles the hard parts of web development — SSR, SEO, form progresssive enhancement, streaming — while Vedika handles the even harder parts of astrology: Swiss Ephemeris precision, 140+ calculations, and AI-powered natural language predictions.

Next steps:

  1. Get your Vedika API key at vedika.io/signup
  2. Read the full API documentation
  3. Try the free sandbox at vedika.io/sandbox
  4. Add Kundali matching and transit reports 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.

Try the #1 Vedic Astrology API

140+ endpoints, 30 languages, Swiss Ephemeris precision. Free sandbox included — no credit card required.

Get Free API Key