All engineering notes

Application Architecture / 7 min read / September 2, 2026

Building a Flutter App and Next.js Admin on One Supabase Backend

How a mobile application and administration dashboard can share one PostgreSQL-backed content platform while keeping their responsibilities separate.

FlutterNext.jsSupabasePostgreSQL
A Flutter mobile application and Next.js administration dashboard connected to Supabase and PostgreSQL

System flow

Architecture

Next.js Administration Dashboard
Flutter Mobile App
Supabase APIs / Authentication
Shared PostgreSQL Content Model

I built the Dua & Dhikr Platform as one ecosystem with two interfaces: a Flutter mobile application for consuming content and a Next.js dashboard for managing it. Both use the same Supabase and PostgreSQL backend, but they do not have the same responsibilities or permissions.

Keeping those concerns separate made the system easier to reason about. The mobile app could stay focused on reading, counters, Qibla functionality, and offline behavior. The administration interface could handle authenticated content workflows without shipping those controls inside the consumer app.

One platform, not two unrelated applications

The Flutter app and Next.js dashboard share a content model. A dua or dhikr entry should not need to be recreated for each client, and display ordering should not be hidden in a mobile release.

PostgreSQL provides the source of truth. Supabase exposes the application-facing APIs and authentication capabilities. Each client then implements the workflow appropriate to its users.

This produces a useful boundary:

  • mobile decides how published content is presented and used;
  • administration decides how authorized people create and maintain that content;
  • backend enforces the shared structure and access rules.

The architecture is still one product because both interfaces operate on the same lifecycle.

Flutter responsibilities

The mobile application displays structured dua and dhikr content on Android and iOS. It also contains interaction-specific features that do not belong in an admin dashboard: configurable counters, target-based counting, haptic feedback, and a Qibla compass.

The app consumes content that is eligible for display and respects the ordering stored in the database. It does not expose editorial controls merely because it can reach the same backend.

Offline caching was a separate concern. Mobile access should remain useful when connectivity is intermittent, but the cache cannot quietly become a second permanent source of truth. I treated cached content as a local representation of published backend data and designed refresh behavior around that model.

This raises practical questions:

  • when is cached content considered usable?
  • how does the app learn that visibility or ordering changed?
  • what should happen when a refresh fails?
  • which functions are entirely local, such as an in-progress counter?

Those decisions belong to the mobile experience rather than the database alone.

Next.js administration responsibilities

The administration platform provides authenticated content operations. Its workflows include creating, editing, and deleting content; searching and paging through records; changing visibility; and controlling display order.

Search and pagination matter because an administrative list that loads every row will become awkward long before the consumer application does. The dashboard asks the backend for the subset needed for the current view and preserves enough state to keep editing predictable.

Visibility is more than a cosmetic switch. It is part of the publication model. The mobile query should only expose content intended for consumption, while authenticated administration queries need to see draft or hidden records.

Ordering also belongs in data. Hard-coding an order in Flutter would require an application release for an editorial change. Storing it in PostgreSQL lets the administrator adjust presentation while the mobile client applies a stable sort rule.

Backend responsibilities

The shared PostgreSQL schema defines the fields and relationships that both clients understand. Supabase provides the interface to that data and authentication where appropriate.

I kept access modes distinct. The administration dashboard needs authenticated CRUD operations. The mobile app mostly needs published read access. Sharing a backend does not mean sharing one unrestricted credential or one identical policy.

Database constraints and permission rules protect invariants that should not depend on client code. The clients can validate for user experience, but the backend remains responsible for rejecting operations outside the data model or access policy.

Environment configuration was also kept out of source files. Public client configuration and privileged server-side configuration have different sensitivity. A browser or mobile binary cannot safely contain an administrative secret merely because it is convenient during development.

What became difficult

The most interesting problems appeared at the boundaries.

An item could be valid in the database but absent on mobile because its visibility state excluded it. A reordered item could look unchanged because the mobile cache had not refreshed. An admin search could return the expected row while the published-content query correctly did not.

Debugging required identifying which representation I was looking at: raw administrative data, the published query result, or cached mobile content. Treating them as the same view would have produced incorrect fixes.

The separate interfaces also needed consistent types and field meaning. A database column named one way, a generated API type another way, and a Flutter model with a third interpretation creates drift even though everything uses one database.

Why the separation is cleaner

Putting administration inside the Flutter app would combine consumer navigation, privileged authentication, editorial forms, and release cycles. It would also increase the impact of a mistake in mobile authorization.

The Next.js dashboard gives administrative workflows their own interface and deployment lifecycle. Flutter stays focused on the mobile experience. Supabase and PostgreSQL connect them through a shared, policy-controlled model.

The result is not a microservice system. It is a small full-stack platform with clear client boundaries. That is enough architecture for the problem without adding a complex CMS or duplicating content storage.

What I learned

Sharing a backend works best when clients share data semantics but not every capability. The Flutter app, Next.js dashboard, Supabase layer, and PostgreSQL database each have a defined responsibility.

That separation made offline caching, publication state, ordering, authentication, and CRUD behavior easier to discuss and debug. It also reinforced a broader infrastructure lesson: understanding the workload above the database leads to better access and deployment decisions below it.