A personal developer dashboard gives you one place to see what you are building, where each project is deployed, and whether its latest deployment succeeded. It is also a practical full-stack project: you will design a data model, build an API, add authentication, and deploy a frontend backed by persistent storage.
This tutorial outlines a realistic first version using Next.js, TypeScript, PostgreSQL, and Prisma. The dashboard will track projects and deployment records, while leaving room to connect a hosting provider later. It is designed as a learning project—not as a production monitoring service.
Key takeaways
- Keep projects and deployment attempts as separate records so you can show deployment history.
- Put authorization checks in the API, not just in the frontend.
- Use a database for durable project data; browser storage alone is not enough.
- Start with manual deployment updates, then add provider integrations only when the core app works.
- Treat deployment credentials and webhook events as sensitive inputs.
Plan the dashboard before coding
Choose a focused first version. A useful dashboard can show:
- Project name, description, repository URL, and production URL.
- The current deployment status and the time it was updated.
- A short list of recent deployment attempts.
- Actions to create, edit, archive, or delete a project.
Do not start by trying to reproduce every feature of a hosting platform. The dashboard is an index of your work, not a replacement for provider logs, build output, or incident alerting.
A project can have many deployments. Keeping that history lets the interface distinguish the current state from a previous successful deployment. For example, a failed attempt should not erase the fact that an earlier build is still serving traffic.
Architecture and stack
A straightforward architecture keeps the browser, API, and database responsibilities separate:
Browser (Next.js UI)
|
| authenticated HTTPS requests
v
Next.js API routes / server actions
|
| Prisma queries
v
PostgreSQL
Use Next.js with TypeScript for the frontend and API, PostgreSQL for persistent data, and Prisma for schema management and queries. This keeps the initial project in one application while still giving you clear boundaries: the browser renders the dashboard, server-side code validates and authorizes requests, and the database stores records.
You can deploy the application to a platform that supports your Next.js app and connect it to a managed PostgreSQL database. Keep database credentials in server-side environment variables. Never expose them through a NEXT_PUBLIC_ variable or commit them to source control.
Design the data model
A minimal relational model needs users, projects, and deployments. A user owns projects; each project can have many deployment records.
model User {
id String @id @default(cuid())
email String @unique
projects Project[]
createdAt DateTime @default(now())
}
model Project {
id String @id @default(cuid())
ownerId String
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
name String
description String @default("")
repositoryUrl String?
productionUrl String?
deployments Deployment[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([ownerId, updatedAt])
}
model Deployment {
id String @id @default(cuid())
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
status String
environment String @default("production")
commitSha String?
deploymentUrl String?
deployedAt DateTime?
createdAt DateTime @default(now())
@@index([projectId, createdAt])
}
For a learning project, status might contain values such as queued, building, ready, failed, or canceled. In a larger application, represent status as a database enum or validate it against an explicit list in your application code. Do not accept arbitrary status strings from the client.
The model intentionally does not store provider access tokens. If you later need provider credentials, use a carefully designed secret-storage approach and restrict access to server-side code. A simpler first release can record deployment details manually without storing any external credentials.
Build the API around ownership
Design endpoints around the records the dashboard needs. For example:
| Method | Endpoint | Purpose |
|---|---|---|
GET | /api/projects | List the signed-in user's projects |
POST | /api/projects | Create a project |
PATCH | /api/projects/:id | Update an owned project |
DELETE | /api/projects/:id | Delete an owned project |
GET | /api/projects/:id/deployments | List deployments for an owned project |
POST | /api/projects/:id/deployments | Add a deployment record |
Every protected request should identify the user from a verified server-side session. Then query or mutate records using both the record ID and the authenticated user's ID. Checking ownership only in the UI is insufficient: a user can make requests without using your interface.
A route handler's core pattern looks like this. The authentication and validation helpers are placeholders for functions you implement with your chosen session and validation libraries; do not copy this as a complete route without supplying them.
export async function GET() {
const user = await requireUser();
const projects = await prisma.project.findMany({
where: { ownerId: user.id },
include: {
deployments: {
orderBy: { createdAt: "desc" },
take: 1,
},
},
orderBy: { updatedAt: "desc" },
});
return Response.json(projects);
}
For create and update routes, validate request bodies on the server: require a project name, limit field lengths, and accept only supported URL formats. Return appropriate error responses without leaking stack traces or database details. Consider rate limits if the application becomes publicly accessible.
Authentication and privacy considerations
For a personal dashboard, you can use an established authentication library or identity provider rather than implementing password handling yourself. Configure secure session cookies, require authentication for dashboard data, and make sign-out invalidate the session according to your authentication setup.
Apply these rules consistently:
- Derive the user ID from the verified session, never from a client-supplied
ownerId. - Scope reads, updates, and deletes to that user ID.
- Validate input on the server, even when the form also validates it in the browser.
- Avoid collecting data you do not need. Repository and deployment URLs can reveal project information.
- Keep secrets out of logs, client bundles, and error messages.
If you later add Git hosting or deployment-provider integrations, use the provider's documented OAuth or token flow, request only necessary permissions, and provide a way to revoke the connection. Do not put long-lived tokens in local storage.
Show deployment status honestly
For the first iteration, let the user add a deployment record through a form. Include its environment, status, URL, and optional commit SHA. Display the latest record prominently, but make the history available so a failed recent attempt is not mistaken for the state of every environment.
A manual status is only as accurate as the person updating it. If you need automatic updates, integrate with the deployment provider through its supported API or webhook mechanism. Verify webhook signatures using the provider's documented method, reject invalid events, and make event processing idempotent so a retried event does not create duplicate records. Keep webhook secrets server-side and avoid trusting an event merely because it contains a familiar project name.
Treat the stored deployment status as a report from a provider or user, not independent proof that a site is healthy. A ready state does not necessarily test application behavior, availability, or the correctness of production data. Link to the provider's deployment page when available so users can inspect authoritative logs.
Build the interface in practical steps
A dashboard page can be assembled incrementally:
- Create the project list. Show a useful empty state for a new account, plus a form to add a project.
- Add project details. Display repository and production links, with clear labels and safe link handling.
- Add deployment history. Show status, environment, date, and deployment URL for each record.
- Add editing and deletion. Confirm destructive actions and handle API errors visibly.
- Add loading and error states. Do not leave a blank page when a request is pending or fails.
- Check responsive layouts. Make project cards and forms usable on narrow screens.
Keep client-side state focused on presentation. The database is the source of truth, so refreshes and sign-ins on another device should show the same projects. Add pagination if the list grows instead of loading an unbounded history into the page.
Staged implementation plan
A staged plan keeps the project deliverable and makes debugging easier:
Stage 1: Scaffold and layout
Create the Next.js TypeScript app, add the dashboard shell, and build static project cards with sample data. Confirm that the core layout works on desktop and mobile.
Stage 2: Database and project CRUD
Add PostgreSQL and Prisma, create the schema, and implement project creation, listing, editing, and deletion. Test that data remains after restarting the app.
Stage 3: Authentication and authorization
Add sign-in, protect dashboard routes, and scope every database operation to the authenticated user. Test with two accounts to confirm that one account cannot read or alter the other's records.
Stage 4: Deployment records
Add deployment creation and history. Validate status values and URLs, then present the latest deployment without hiding earlier attempts.
Stage 5: Deploy and harden
Deploy the application, configure production environment variables, run database migrations using the deployment workflow, and test sign-in and CRUD behavior in production. Review logs for accidental sensitive data and ensure errors are useful without exposing internals.
Stage 6: Optional provider integration
Only after the manual workflow is reliable, choose one provider and implement its documented integration. Add signature verification, duplicate-event handling, and tests for valid, invalid, and repeated events. Keep the manual entry path as a fallback if the integration is unavailable.
Common mistakes and troubleshooting
- Projects disappear after redeploying: Check that the app uses a persistent database rather than an ephemeral local file or in-memory store.
- Users can see records they do not own: Audit every API query for ownership scoping; hiding navigation is not authorization.
- Status labels disagree with reality: Clarify whether the value is manual or provider-reported and show when it was last updated.
- A valid-looking URL causes unexpected behavior: Validate the input for the intended purpose and render it safely. If the server later fetches user-supplied URLs, additional SSRF defenses are required; parsing a URL or checking its hostname alone is not sufficient.
- Production works differently from local development: Verify environment variables, database connectivity, migration execution, and cookie settings in the deployed environment.
- Webhook deliveries create duplicates: Use a provider event ID or another stable idempotency key and enforce uniqueness where appropriate.
Conclusion
A developer dashboard project is a strong full-stack exercise because it connects a useful interface to real concerns: relational data, authenticated APIs, ownership checks, and deployment workflows. Start with project CRUD and manual deployment records, make privacy and authorization part of the design, and add provider automation only when the core experience is dependable. That scope produces a useful personal tool while leaving clear paths for future improvements.
