Skip to main content
RapidDev - Software Development Agency

How to Build a Personalization System with Lovable

Build a behavioral personalization system in Lovable that tracks user_events, computes audience segments from rule builders, serves content variants per segment, and runs A/B tests — all backed by Supabase. A scheduled Edge Function recomputes segment membership nightly so your content always matches the right audience without manual tagging.

What you'll build

  • Event tracking system that logs user_events with event type, properties JSON, and session context
  • Visual segment rule builder using condition groups (AND/OR) with an attribute picker
  • Content variants table linking content blocks to specific segments with a priority ranking
  • A/B test configuration with traffic split percentages and a variant assignment system
  • Scheduled Supabase Edge Function that recomputes user segment membership nightly
  • Analytics dashboard showing segment sizes, variant impressions, and conversion lift per test
  • Real-time personalized content renderer that swaps variants based on the current user's segments
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced17 min read4–5 hoursLovable Pro or higherApril 2026RapidDev Engineering Team
TL;DR

Build a behavioral personalization system in Lovable that tracks user_events, computes audience segments from rule builders, serves content variants per segment, and runs A/B tests — all backed by Supabase. A scheduled Edge Function recomputes segment membership nightly so your content always matches the right audience without manual tagging.

What you're building

User events are the raw material of personalization. Every action — page view, button click, purchase, search query — gets logged to a user_events table with an event_name, properties JSON, and the user's session ID. These events power two things: segment computation and A/B test assignment.

Segments are defined by rules — conditions like 'user has done event X more than Y times in the last Z days' or 'user property equals value'. The rule builder stores these as a JSONB array of condition groups. A nightly scheduled Edge Function evaluates every active segment rule against the event log, then inserts or deletes rows in user_segment_memberships to reflect the current segment membership. This batch approach is far more scalable than computing segments on every page load.

A/B tests link to a base content block and two or more variant content blocks. When a user visits a page, a deterministic hash of their user ID and test ID assigns them to a variant consistently. This assignment never changes for the same user + test combination, ensuring consistent experience. Impressions and conversions are logged back to user_events so you can measure lift.

The content variants table links content slots (hero_heading, pricing_cta, feature_section) to segment-specific copy, images, or component configurations. A usePersonalization hook fetches the current user's segments on mount and returns the correct variant for each slot.

Final result

A production-ready personalization system with rule-based segments, content variants, and A/B testing — built entirely in Lovable and Supabase.

Tech stack

LovableFrontend dashboard and personalized UI
SupabaseDatabase, Auth, Edge Functions
Supabase Edge FunctionsSegment computation and variant serving (Deno)
shadcn/uiRule builder UI and dashboard components
RechartsA/B test and segment analytics charts
React Hook Form + ZodSegment rule form validation

Prerequisites

  • Lovable Pro account for multi-function and Edge Function generation
  • Supabase project with Edge Functions enabled (Pro plan recommended for pg_cron)
  • Supabase service role key saved to Cloud tab → Secrets as SUPABASE_SERVICE_ROLE_KEY
  • Basic understanding of product analytics concepts (events, funnels, segments)
  • Optional: existing Lovable app with user auth to add personalization to

Build steps

1

Set up the personalization database schema

Prompt Lovable to create all the tables that power event tracking, segment computation, content variants, and A/B testing. Getting the schema right before building the UI avoids costly rewrites later.

prompt.txt
1Create a behavioral personalization system schema in Supabase:
2
3- user_events: id, user_id (nullable, for anonymous events), session_id, event_name (text), properties (jsonb), page_url (text), created_at
4- segments: id, name, description, rules (jsonb array of condition groups), is_active (bool), recomputed_at, member_count (int, denormalized)
5- user_segment_memberships: id, user_id, segment_id, assigned_at, expires_at (nullable). Unique constraint on (user_id, segment_id).
6- content_slots: id, slot_key (text, unique, e.g. 'hero_heading'), description, default_content (jsonb)
7- content_variants: id, slot_key, segment_id (nullable, null = default), content (jsonb), priority (int default 0)
8- ab_tests: id, name, slot_key, traffic_split (jsonb, e.g. {"control": 50, "variant_a": 50}), status (draft|running|stopped|completed), started_at, ended_at
9- ab_test_variants: id, test_id, variant_key (e.g. 'control', 'variant_a'), content (jsonb), impressions (int default 0), conversions (int default 0)
10- user_ab_assignments: id, user_id, test_id, variant_key, assigned_at. Unique constraint on (user_id, test_id).
11
12RLS:
13- user_events: authenticated users can INSERT their own events (user_id = auth.uid()). Service role can SELECT all.
14- user_segment_memberships: users can SELECT their own memberships. Service role can INSERT/UPDATE/DELETE.
15- content_slots, content_variants, segments, ab_tests, ab_test_variants: public SELECT. Service/admin-only writes.
16- user_ab_assignments: users can SELECT/INSERT their own assignments.
17
18Create index: CREATE INDEX idx_events_user_name ON user_events(user_id, event_name, created_at DESC).

Pro tip: Add a GIN index on user_events.properties to enable fast filtering by JSON properties: CREATE INDEX idx_events_properties ON user_events USING gin(properties). This makes segment rules that filter on event properties (e.g. plan='pro') much faster.

Expected result: All tables are created with correct constraints and RLS policies. Indexes are in place. The app shell renders in preview.

2

Build the segment rule builder UI

Create the segment editor page with a visual rule builder. Users can add condition groups (OR between groups, AND within a group) and choose from attribute types like event count, event property value, and user property. The rules are serialized as JSONB and saved to the segments table.

prompt.txt
1Build a Segment editor page at src/pages/SegmentEditor.tsx.
2
3The rule structure as TypeScript types:
4type Condition = { attribute: 'event_count' | 'event_property' | 'user_property', event?: string, property?: string, operator: 'eq' | 'gt' | 'lt' | 'contains' | 'in', value: string | number, timeframe_days?: number }
5type ConditionGroup = { operator: 'AND', conditions: Condition[] }
6type SegmentRules = { operator: 'OR', groups: ConditionGroup[] }
7
8UI requirements:
9- Page header with segment name Input and description Textarea
10- Rule builder section shows condition groups as Cards
11- Each group Card has a header 'Group N' and an 'Add condition' Button
12- Each condition row has:
13 - Attribute Select: 'Did event', 'Event property', 'User property'
14 - When 'Did event': event name Input, operator Select (more than/less than/exactly), count Input, timeframe Select (last 7/30/90 days)
15 - When 'Event property': event name Input, property name Input, operator Select (equals/contains/is in), value Input
16 - A Remove button (X icon) for each condition
17- Between groups show an 'OR' Badge
18- 'Add Group' Button at the bottom adds a new empty ConditionGroup
19- 'Save Segment' Button that serializes the form state to SegmentRules JSONB and upserts to the segments table
20- Show a 'Preview Size' Button that calls a Supabase Edge Function evaluate-segment with the current rules and returns an estimated member count

Pro tip: Store the rule builder form state in React useState as the SegmentRules object directly. This makes serialization to JSONB trivial — just JSON.stringify the state before saving.

Expected result: The segment editor renders a dynamic rule builder. Adding conditions updates the in-memory rules object. Saving writes the JSONB rules to Supabase. Preview Size returns an estimated count.

3

Build the nightly segment computation Edge Function

Create a Supabase Edge Function that iterates over all active segments, evaluates their JSONB rules against the user_events table, and updates user_segment_memberships. This runs nightly to keep segment membership current.

supabase/functions/compute-segments/index.ts
1// supabase/functions/compute-segments/index.ts
2import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
3import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
4
5const corsHeaders = { 'Content-Type': 'application/json' }
6
7serve(async (req: Request) => {
8 // Allow manual trigger via POST with optional segment_id filter
9 const body = req.method === 'POST' ? await req.json().catch(() => ({})) : {}
10 const segmentIdFilter = body.segment_id as string | undefined
11
12 const supabase = createClient(
13 Deno.env.get('SUPABASE_URL') ?? '',
14 Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
15 )
16
17 // Fetch active segments
18 let query = supabase.from('segments').select('*').eq('is_active', true)
19 if (segmentIdFilter) query = query.eq('id', segmentIdFilter)
20 const { data: segments, error } = await query
21 if (error) return new Response(JSON.stringify({ error: error.message }), { status: 500, headers: corsHeaders })
22
23 const results: Record<string, number> = {}
24
25 for (const segment of segments ?? []) {
26 try {
27 // Evaluate segment rules — simplified example for event_count conditions
28 const rules = segment.rules as { operator: string, groups: Array<{ conditions: Array<{ attribute: string, event?: string, operator: string, value: number, timeframe_days?: number }> }> }
29
30 // Get all users who qualify
31 const qualifyingUsers = await evaluateSegmentRules(supabase, rules)
32
33 // Get current members
34 const { data: currentMembers } = await supabase
35 .from('user_segment_memberships')
36 .select('user_id')
37 .eq('segment_id', segment.id)
38
39 const currentSet = new Set((currentMembers ?? []).map(m => m.user_id))
40 const newSet = new Set(qualifyingUsers)
41
42 // Add new members
43 const toAdd = qualifyingUsers.filter(uid => !currentSet.has(uid))
44 if (toAdd.length > 0) {
45 await supabase.from('user_segment_memberships').insert(
46 toAdd.map(uid => ({ user_id: uid, segment_id: segment.id, assigned_at: new Date().toISOString() }))
47 )
48 }
49
50 // Remove former members
51 const toRemove = [...currentSet].filter(uid => !newSet.has(uid))
52 if (toRemove.length > 0) {
53 await supabase.from('user_segment_memberships')
54 .delete()
55 .eq('segment_id', segment.id)
56 .in('user_id', toRemove)
57 }
58
59 const memberCount = qualifyingUsers.length
60 await supabase.from('segments').update({ member_count: memberCount, recomputed_at: new Date().toISOString() }).eq('id', segment.id)
61 results[segment.name] = memberCount
62 } catch (err) {
63 results[`${segment.name}_error`] = -1
64 }
65 }
66
67 return new Response(JSON.stringify({ computed: results }), { headers: corsHeaders })
68})
69
70async function evaluateSegmentRules(supabase: ReturnType<typeof createClient>, rules: any): Promise<string[]> {
71 // Simplified: handles event_count conditions only
72 const allUserIds = new Set<string>()
73 for (const group of rules.groups) {
74 const groupUserIds = await evaluateConditionGroup(supabase, group)
75 groupUserIds.forEach(id => allUserIds.add(id))
76 }
77 return [...allUserIds]
78}
79
80async function evaluateConditionGroup(supabase: ReturnType<typeof createClient>, group: any): Promise<string[]> {
81 let resultSet: Set<string> | null = null
82 for (const condition of group.conditions) {
83 const users = await evaluateCondition(supabase, condition)
84 const userSet = new Set(users)
85 resultSet = resultSet === null ? userSet : new Set([...resultSet].filter(x => userSet.has(x)))
86 }
87 return [...(resultSet ?? [])]
88}
89
90async function evaluateCondition(supabase: ReturnType<typeof createClient>, condition: any): Promise<string[]> {
91 const since = condition.timeframe_days
92 ? new Date(Date.now() - condition.timeframe_days * 86400000).toISOString()
93 : new Date(0).toISOString()
94
95 const { data } = await supabase.rpc('get_users_by_event_count', {
96 p_event_name: condition.event,
97 p_min_count: condition.value,
98 p_since: since,
99 })
100 return (data ?? []).map((r: any) => r.user_id)
101}

Expected result: The Edge Function runs and populates user_segment_memberships based on event data. The segments table has updated member_count and recomputed_at values.

4

Build the A/B test manager and variant assignment

Create the A/B testing UI and the deterministic variant assignment hook. Test managers can create tests, configure traffic splits, and see live impression/conversion counts. The frontend hook assigns users to variants using a hash of their user ID.

prompt.txt
1Build two things:
2
31. A/B Test manager page at src/pages/AbTests.tsx:
4- List all ab_tests as Cards showing: name, status Badge (draft=gray, running=green, stopped=red), slot_key, traffic split as a horizontal progress bar
5- Each running test shows: total impressions, total conversions, conversion rate per variant as a Recharts BarChart
6- 'Create Test' Button opens a Dialog:
7 - Test name Input
8 - Content slot Select (from content_slots table)
9 - Variant configuration: add up to 4 variants each with a key (text) and percentage (number). Percentages must sum to 100 show validation error if not
10 - Content editor per variant: a Textarea for the variant's content JSON
11- 'Start Test' and 'Stop Test' Buttons that update the status field
12
132. A hook at src/hooks/useAbTest.ts:
14- Accepts testId: string and userId: string
15- Looks up the test in ab_tests table
16- Checks user_ab_assignments for an existing assignment
17- If no assignment exists, uses a deterministic hash: variant index = sum(userId.charCodeAt(i)) % total_weight, maps to a variant based on traffic_split percentages
18- Inserts the assignment to user_ab_assignments
19- Logs an 'ab_test_impression' user_event
20- Returns the assigned variant's content
21- Export a logConversion(testId, userId) function that logs an 'ab_test_conversion' event

Expected result: Test managers can create and manage A/B tests. The useAbTest hook deterministically assigns users to variants. Impressions increment as users encounter tests. Conversions can be logged from any component.

5

Build the personalized content renderer

Create the usePersonalization hook that fetches the current user's segment memberships and returns the highest-priority content variant for any given slot. Build a demo PersonalizedHero component that shows different headline copy per segment.

prompt.txt
1Build two things:
2
31. Hook at src/hooks/usePersonalization.ts:
4- On mount, fetch the current user's segment IDs from user_segment_memberships WHERE user_id = auth.uid()
5- Also check user_ab_assignments for active tests
6- Expose a function getVariant(slotKey: string): ContentVariant that:
7 1. Fetches content_variants WHERE slot_key = slotKey
8 2. Among variants with a segment_id, finds those matching the user's segments
9 3. Returns the highest priority matching variant, or the default variant (segment_id IS NULL) as fallback
10 4. Caches results in a Map keyed by slotKey to avoid re-fetching on every render
11- Expose loading and error states
12
132. Demo page at src/pages/PersonalizedDemo.tsx:
14- Uses usePersonalization hook
15- Shows a hero section where the heading, subheading, and CTA text all come from getVariant('hero_heading'), getVariant('hero_subheading'), getVariant('hero_cta')
16- While loading, shows skeleton Skeletons for each section
17- Shows a debug panel (only in dev mode) listing the user's current segments as Badges
18- Add a 'Track Event' Button that calls supabase.from('user_events').insert({ event_name: 'demo_cta_click', user_id: user.id, properties: {} }) and shows a Toast confirmation

Expected result: The PersonalizedDemo page shows different content based on the current user's segments. Users in different segments see different hero copy. The debug panel shows segment memberships.

Complete code

src/hooks/usePersonalization.ts
1import { useState, useEffect, useCallback, useRef } from 'react'
2import { supabase } from '@/lib/supabase'
3import { useAuth } from '@/hooks/useAuth'
4
5interface ContentVariant {
6 id: string
7 slot_key: string
8 segment_id: string | null
9 content: Record<string, unknown>
10 priority: number
11}
12
13interface PersonalizationState {
14 segmentIds: string[]
15 variants: Map<string, ContentVariant>
16 loading: boolean
17 error: string | null
18}
19
20export function usePersonalization() {
21 const { user } = useAuth()
22 const [state, setState] = useState<PersonalizationState>({
23 segmentIds: [],
24 variants: new Map(),
25 loading: true,
26 error: null,
27 })
28 const variantCache = useRef<Map<string, ContentVariant>>(new Map())
29
30 useEffect(() => {
31 if (!user?.id) {
32 setState(s => ({ ...s, loading: false }))
33 return
34 }
35
36 async function loadSegments() {
37 const { data, error } = await supabase
38 .from('user_segment_memberships')
39 .select('segment_id')
40 .eq('user_id', user!.id)
41
42 if (error) {
43 setState(s => ({ ...s, error: error.message, loading: false }))
44 return
45 }
46
47 const segmentIds = (data ?? []).map(r => r.segment_id)
48 setState(s => ({ ...s, segmentIds, loading: false }))
49 }
50
51 loadSegments()
52 }, [user?.id])
53
54 const getVariant = useCallback(
55 async (slotKey: string): Promise<ContentVariant | null> => {
56 if (variantCache.current.has(slotKey)) {
57 return variantCache.current.get(slotKey)!
58 }
59
60 const { data: allVariants } = await supabase
61 .from('content_variants')
62 .select('*')
63 .eq('slot_key', slotKey)
64 .order('priority', { ascending: false })
65
66 if (!allVariants?.length) return null
67
68 const segmentVariants = allVariants.filter(
69 v => v.segment_id && state.segmentIds.includes(v.segment_id)
70 )
71
72 const chosen =
73 segmentVariants[0] ?? allVariants.find(v => v.segment_id === null) ?? null
74
75 if (chosen) variantCache.current.set(slotKey, chosen)
76 return chosen
77 },
78 [state.segmentIds]
79 )
80
81 const trackEvent = useCallback(
82 async (eventName: string, properties: Record<string, unknown> = {}) => {
83 if (!user?.id) return
84 await supabase.from('user_events').insert({
85 user_id: user.id,
86 event_name: eventName,
87 properties,
88 page_url: window.location.href,
89 })
90 },
91 [user?.id]
92 )
93
94 return { ...state, getVariant, trackEvent }
95}

Customization ideas

Real-time segment membership

Instead of nightly batch computation, add a Supabase Database Trigger on user_events that calls a lightweight Edge Function to check if the new event tips the user into or out of any active segment. This gives you near-real-time segment updates for high-value events like 'completed_purchase' without running the full nightly job.

Funnel visualization for segment journeys

Add a funnel chart using Recharts that shows how users progress through a defined sequence of events (e.g. viewed_pricing → started_trial → completed_purchase). Filter the funnel by segment to compare conversion rates between personalized and non-personalized user groups.

Geo-based segmentation

Add a country column to user_events by reading the CF-IPCountry header in an Edge Function that wraps your event ingestion. Add geo-condition support to the segment rule builder so you can create segments like 'users from Germany' and serve localized content variants to them.

Personalized email campaigns

Add a campaigns table that links an email template to a segment and a trigger condition. A scheduled Edge Function checks for users who entered a segment in the last 24 hours and sends them the campaign email via Resend. Track opens and clicks as new user_events to close the loop.

Segment export for ad targeting

Add an Export button on each segment that generates a CSV of user emails for that segment. Use this to upload custom audiences to Facebook Ads or Google Ads. Add a last_exported_at column to segments so you know when each segment was last synced to your ad platform.

Common pitfalls

Pitfall: Computing segment membership on every page load

How to avoid: Use the batch nightly computation pattern. The compute-segments Edge Function populates user_segment_memberships once per day. The frontend reads from this pre-computed table, which is a simple indexed lookup taking milliseconds.

Pitfall: Not indexing the user_events table by user_id and created_at

How to avoid: Add: CREATE INDEX idx_events_user_name ON user_events(user_id, event_name, created_at DESC). For property filtering, also add the GIN index on the properties JSONB column.

Pitfall: Using a random variant assignment instead of deterministic hashing

How to avoid: Use a deterministic hash of the user ID and test ID to always produce the same variant for the same user. Store the assignment in user_ab_assignments on first encounter so the hash computation only happens once per user per test.

Pitfall: Allowing RLS bypass on user_events for anonymous users

How to avoid: Rate-limit anonymous event ingestion through an Edge Function that checks the client IP. For authenticated users, use Supabase RLS with user_id = auth.uid(). Consider only tracking anonymous events for page views, not for segment-affecting actions.

Best practices

  • Keep content variants as pure data (JSONB) rather than as React components stored in the database. The database stores text, numbers, and image URLs — the Lovable frontend maps these to components. This separates content from presentation.
  • Use the priority column on content_variants to handle the case where a user is in multiple segments that all have a variant for the same slot. The highest-priority variant wins — no ambiguity.
  • Add a valid_from and valid_until timestamp to content_variants so you can schedule seasonal personalization (holiday sale copy, new year promotions) without manual toggling.
  • Log event_name values using a consistent naming convention like 'noun_verb' (e.g. 'pricing_page_viewed', 'trial_started', 'plan_upgraded'). Inconsistent naming makes segment rules hard to build and audit.
  • Run A/B tests for at minimum 2 weeks and wait for statistical significance before declaring a winner. Do not stop a test early just because one variant is leading — early leaders often reverse.
  • Add a holdout group to every A/B test — a small percentage of users (5%) who always see the control. This lets you measure the cumulative effect of all personalizations over time.
  • Archive rather than delete old segments and A/B tests. Historical data in user_segment_memberships and user_ab_assignments is valuable for understanding long-term trends and cannot be reconstructed after deletion.

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a behavioral personalization system with a JSONB rules engine in PostgreSQL. My segment rules are stored as { operator: 'OR', groups: [{ operator: 'AND', conditions: [{ attribute: 'event_count', event: 'page_viewed', operator: 'gt', value: 3, timeframe_days: 30 }] }] }. Write a PostgreSQL function that takes a segment_id, evaluates its rules against my user_events table, and returns a set of qualifying user_ids. Use CTEs to make it readable. Handle event_count and event_property condition types.

Lovable Prompt

Add a live segment preview to the Segment editor. When the user clicks 'Preview Segment', call the compute-segments Edge Function with the current unsaved rules and show a SlideOver panel from the right. The panel shows: estimated member count, a sample of 10 matching users (show first name and email from auth schema using service role), and a bar chart showing what percentage of all users qualify. Show a loading Spinner while the Edge Function runs.

Build Prompt

In Supabase, set up a pg_cron job to run the compute-segments Edge Function every night at 2am UTC. Use the cron.schedule function: SELECT cron.schedule('nightly-segment-recompute', '0 2 * * *', $$SELECT net.http_post(url := current_setting('app.settings.supabase_url') || '/functions/v1/compute-segments', headers := jsonb_build_object('Authorization', 'Bearer ' || current_setting('app.settings.service_role_key')))$$). Make sure the pg_net and pg_cron extensions are enabled in your Supabase project.

Frequently asked questions

What is the difference between a segment and an A/B test in this system?

A segment is a group of users defined by their behavior — they get a specific content variant permanently as long as they're in the segment. An A/B test randomly splits users between variants to measure which performs better. Segments drive permanent personalization; A/B tests drive controlled experiments. You can combine them by running an A/B test only within a specific segment.

How do I track events from the Lovable frontend?

Use the trackEvent function returned by usePersonalization. Call it in onClick handlers, useEffect hooks for page views, or after form submissions. Events are inserted directly to user_events via Supabase client. For anonymous users (before sign-in), you can still track events using a session_id stored in localStorage and attach the user_id retroactively after sign-in.

Can I use this system without the nightly Edge Function by computing segments on demand?

Yes, but only for small datasets. If you have fewer than 1,000 users and fewer than 100,000 events, on-demand segment evaluation is fast enough. Call the compute-segments Edge Function with a specific segment_id after any significant user action. For larger scale, the nightly batch approach is required to stay within Supabase Edge Function timeout limits.

How do I measure whether personalization is actually working?

Compare conversion rates between personalized and non-personalized users using the A/B test holdout group (5% always sees default content). Track your primary conversion event (e.g. 'trial_started') as an A/B test conversion. After 2+ weeks of data, compare conversion rates between the control (no personalization) and treatment (personalized) groups to calculate lift.

What GDPR considerations apply to this event tracking system?

You must disclose event tracking in your privacy policy. For EU users, obtain consent before logging behavioral events (use a consent management component). Add a user data deletion function that removes all rows from user_events, user_segment_memberships, and user_ab_assignments for a given user_id. Do not log personally identifiable information in the event properties JSON — log IDs and anonymized attributes instead.

Is RapidDev available to help build a more sophisticated personalization engine?

Yes. RapidDev builds production personalization systems including real-time segment evaluation, machine learning-based scoring, and multi-channel delivery (web, email, push). Reach out if your personalization needs go beyond rule-based segments.

How do I prevent the same user from being assigned to a segment indefinitely?

Add an expires_at column to user_segment_memberships. When the nightly compute-segments function runs, it can set expires_at based on the segment's recency window. For example, a 'high-intent' segment based on events in the last 30 days should expire memberships that no longer qualify rather than leaving them indefinitely. The getVariant function should check that the membership has not expired.

Can content variants store full React component configurations?

Content variants store JSONB data — text, numbers, image URLs, and nested objects. The React components in your Lovable app map this data to visual elements. For example, a hero variant might store { heading: 'Try for free', cta_text: 'Start now', cta_color: 'blue' } and your PersonalizedHero component reads these values and applies them. This keeps content manageable without touching code for every copy change.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.