Architecture
Service architecture, components, and data flow
This page describes how 1cal is built under the hood — what its major pieces are, how they connect, and how your data moves through the system when you use the app. Understanding the architecture helps you know what happens when you connect a platform, view your events, or RSVP, so you can trust that your calendar is always up to date. You don't need to be a developer to follow along; the diagrams and descriptions are written to give you a clear mental model of how 1cal works on your behalf.
App Server (Next.js on Vercel)
The core of 1cal runs as a Next.js 14 application hosted on Vercel. It handles everything from serving the pages you see to processing your RSVPs and syncing your event feeds. Vercel's infrastructure means 1cal scales automatically without you noticing any slowdowns.
Middleware (Login Guard)
Every time you navigate to a dashboard page — Home, Calendar, RSVP, or Settings — a lightweight middleware step runs first. It checks whether you have an active login session. If you're signed in, you're passed through immediately. If not, you're redirected to the sign-in page. This keeps your calendar data private.
Server Components (Data Fetching)
When a page loads, 1cal fetches your events and settings directly on the server before sending anything to your browser. This means you see a fully populated page right away instead of watching a loading spinner. Your database credentials never leave the server, which is a security benefit you get for free.
Route Handlers (Actions)
When you do something that changes data — like tapping Going on an event, or pulling down to refresh — a Route Handler processes that action on the server. Route Handlers validate your session, update the database, and optionally sync your response back to the originating platform.
Neon PostgreSQL Database
All of your data — your account, the children you've added, the platforms you've connected, and every event — is stored in a serverless PostgreSQL database powered by Neon. The database schema organises data in a hierarchy: User → Child → SportsPlatform → Event, which means each event is always traceable back to which child and which platform it came from.
ICS Feed Sync (Cron Job)
A scheduled job runs every 30 minutes and fetches the ICS calendar files from each sports platform you've connected. It parses those files and upserts (creates or updates) events in the database. You can also trigger this sync manually by pulling down to refresh on the Calendar screen. Each platform tracks a syncStatus (pending, syncing, synced, or error) so you always know if something went wrong.
Unified ICS Feed Endpoint
Once your events are in the database, 1cal makes them available as a single ICS feed at a unique URL tied to your account. Any calendar app — Apple Calendar, Google Calendar, Outlook — can subscribe to this URL and automatically receive all your events. This URL is shown in your Settings.
Weather Service
When 1cal displays the next upcoming event on your Home screen, it fetches a weather forecast for that event's location and time. It uses Nominatim to translate the venue address into coordinates, then Open-Meteo to retrieve an hourly forecast. Both lookups are cached (location for 24 hours, forecast for 30 minutes) so the app stays fast.
Platform RSVP Sync
When you RSVP through 1cal, the app tries to reflect your choice back to the original platform. If you've stored login credentials for that platform, the sync happens automatically in the background. If not, 1cal opens the platform's event page in a new tab so you can confirm there manually. The event's platformSyncStatus field records whether the sync was automatic, manual, or not applicable.
Navigation Components
- TopBar — the sticky header on every page. Shows the 1cal logo, a notification bell, settings gear, and your avatar. Tapping your avatar lets you sign out.
- BottomNav — the four-tab bar at the bottom of the screen (Home, Calendar, RSVP, Carpool). Highlights the page you're currently on and respects iPhone safe-area spacing.
- PullToRefresh — the pull-down gesture on Calendar and RSVP screens that triggers a fresh sync of your feeds.
Calendar & RSVP Views
- CalendarView — lets you switch between List, Month, and Week layouts. Includes filter chips so you can narrow events by child or sport.
- AgendaView — the list layout, grouping events by date with today highlighted. Each card shows the event type, child, time, location, and RSVP status.
- EventDetailPanel — a slide-up panel with full event details and RSVP buttons, opened when you tap any event.
- RsvpView — a focused view of upcoming events that need a response, with Going / Can't / Maybe buttons and a swipe-to-skip gesture.
Here is how data moves through 1cal during a complete, real-world workflow: connecting a platform, seeing your events, RSVPing, and subscribing your calendar app.
Step 1 — You sign in
When you visit 1cal, the middleware checks for a valid session cookie. Since you're new, there's no session, so you're redirected to /login. You click Sign in with Google, which triggers the NextAuth Google OAuth flow. After Google confirms your identity, NextAuth creates or updates your user record in the database and writes a secure session cookie to your browser. All subsequent requests carry that cookie silently.
Step 2 — You connect a sports platform
In Settings, you add a child and paste in the ICS URL from GameChanger (or TeamSnap, etc.). This creates a Child record and a SportsPlatform record in the database, linked to your user account. The syncStatus is set to pending.
Step 3 — Your events are synced
Within 30 minutes, the Vercel Cron job calls GET /api/cron/sync-feeds. The Route Handler finds every SportsPlatform record across all users, fetches each ICS URL, parses the calendar data using the ical.js library, and upserts each event into the Event table using the combination of uid and platformId as a unique key. This means re-syncing never creates duplicates. The platform's lastSynced timestamp and syncStatus are updated to synced.
If you pull down to refresh on the Calendar screen before the cron runs, the same sync logic is triggered immediately for your platforms specifically.
Step 4 — You view your events
When you open the Calendar page, a Server Component runs on Vercel's servers. It queries the database for all events belonging to platforms connected to your children, then streams the fully rendered HTML to your browser. No loading spinner — the events are there when the page arrives. Client-side components hydrate on top, enabling interactivity like filter chips and view switching.
For the Home page's hero card, the server also calls the weather service: it geocodes the event location via Nominatim and fetches an hourly forecast from Open-Meteo, picking the closest hour to your event's start time. Temperature is shown in °F for US locations and °C elsewhere.
Step 5 — You RSVP to an event
You tap an event to open the EventDetailPanel, then tap Going. This triggers two server calls:
PATCH /api/events/[eventId]/rsvp— saves your choice (rsvpStatus = 'yes') to the database immediately.POST /api/events/[eventId]/platform-rsvp— attempts to sync that choice back to the originating platform:- If you've stored platform credentials, 1cal calls the platform's API automatically and sets
platformSyncStatus = 'synced'. - If no credentials are stored but the platform has a known RSVP URL, 1cal opens that URL in a new tab so you can confirm there.
platformSyncStatusis set to'manual'. - If neither applies,
platformSyncStatusremains'none'.
- If you've stored platform credentials, 1cal calls the platform's API automatically and sets
Step 6 — Your calendar app stays current
In Settings, you copy your personal feed URL (/api/feed/[userId].ics) and subscribe to it in Apple Calendar or Google Calendar. From then on, your calendar app periodically polls that URL. The Route Handler queries all events linked to your account and serialises them as a standard VCALENDAR file. New events, location changes, and time updates flow into your calendar app automatically on its next poll — no manual import needed.
The following table summarises the key architectural choices made in building 1cal, along with the reasoning behind each one.
| Decision | What it means for you | Why it was chosen |
|---|---|---|
| Server Components for data fetching | Pages load with your events already populated — no blank screens or loading spinners on first visit | Keeps database credentials safely on the server and eliminates the round-trip that causes visible loading states |
| ICS polling instead of platform webhooks | Your events update within 30 minutes of a change on the source platform | Sports platforms like GameChanger and TeamSnap don't offer webhooks, so polling every 30 minutes is the most reliable option available and is fast enough for family schedules |
| Prisma ORM over raw SQL | Consistent, reliable data access that's less likely to have subtle bugs | Provides type-safe database queries and handles schema migrations automatically, reducing developer error |
| NextAuth with Google OAuth | You sign in with your existing Google account — no new password to create or remember | Delegates credential security to Google and eliminates the risk of 1cal storing passwords insecurely |
| Unified ICS feed as the sharing mechanism | Any calendar app that supports calendar subscriptions — Apple Calendar, Google Calendar, Outlook — works out of the box | ICS is a universal standard; building a native integration for every calendar app would take far longer and still not cover everyone |
| No global state library (no Redux or Zustand) | The app stays lightweight and fast on mobile devices | Individual components manage their own state with React's built-in useState; the data is always fresh from the server, so a global client-side cache would add complexity without benefit |
| Serverless PostgreSQL (Neon) | The database scales automatically during busy periods (like the start of a sports season) without you noticing | Matches the serverless deployment model of Vercel; no always-on server to maintain or pay for when traffic is low |
| CSS custom properties for theming | Dark mode works without any extra configuration on your device | A lightweight approach that avoids a full CSS-in-JS library, keeping page load times low |
Every architectural choice involves trade-offs. Here are the known limitations of 1cal's current design and the alternatives that were considered.
ICS polling has a delay
Limitation: Changes made on a source platform (like a game being rescheduled) won't appear in 1cal until the next sync cycle, which runs every 30 minutes. In urgent situations — a last-minute cancellation, for example — you may see stale data briefly.
Why it's accepted: Sports platforms don't provide webhooks or real-time push notifications. Polling every 30 minutes is the best available option and is sufficient for the typical use case of weekly family sports schedules.
Workaround: You can trigger an immediate sync by pulling down to refresh on the Calendar screen.
The unified ICS feed is public by user ID
Limitation: Your personal ICS feed URL (/api/feed/[userId].ics) is not protected by a password. Anyone who knows your user ID can subscribe to your event feed.
Why it's accepted: Calendar apps (Apple Calendar, Google Calendar) typically don't support authenticated ICS feeds, so requiring a password would break most subscribers. Your user ID is a random string (a CUID), which makes it very hard to guess.
If this concerns you: Rotating your feed URL (if that feature is available in Settings) would invalidate any existing subscribers.
Weather is only shown for the next upcoming event
Limitation: Weather forecasts are only displayed on the Home screen's hero card for the single next upcoming event. Events further in the future on the Calendar page don't show weather.
Why it's accepted: Weather forecasts beyond a few days are less reliable, and fetching forecasts for every event in your calendar would significantly slow down page loads. The caching strategy (location cached 24 hours, forecast 30 minutes) is tuned for the single-event use case.
Automated RSVP sync requires stored platform credentials
Limitation: For 1cal to sync your RSVP back to the source platform automatically, you need to have saved your platform login credentials in Settings. If you haven't, 1cal falls back to opening the platform website in a new tab, which requires you to confirm there manually.
Why it's accepted: Storing credentials allows seamless automation but requires trust. Users who prefer not to store credentials still get a guided manual fallback.
Google is the only sign-in method
Limitation: You must have a Google account to sign in to 1cal. Email/password sign-in is defined in the database schema (password field exists on the User model) but is not the primary supported flow.
Why it's accepted: Google OAuth eliminates password management entirely for both users and the 1cal team. It is the simplest and most secure starting point for a consumer calendar app.
No offline support
Limitation: 1cal requires an active internet connection. While it is installable as a Progressive Web App (PWA), it does not cache events for offline viewing.
Why it's accepted: Calendar data changes frequently due to the 30-minute sync cycle. Serving stale cached data offline could be more confusing than showing a connection error.