Ship a Next.js 15 SaaS MVP the Right Way
For Indie hackers building SaaS MVPs · Based on PedroTech Next.js 15 App Architecture Method
// TL;DR
Building a SaaS MVP solo means every architectural decision costs time you don't have. The PedroTech Next.js 15 App Architecture Method gives you a proven structure so you don't reinvent routing, data fetching, or API patterns. You'll use async server components for fast dashboard loads, isolate interactive forms as client islands, build backend endpoints in app/api with route.ts, scope your dashboard UI with nested layouts, and add SEO metadata to marketing pages that need to rank. Use it to ship a maintainable MVP fast without accumulating structural debt you'll regret at scale.
How should I structure a SaaS app in Next.js 15?
Separate your marketing surface from your app surface using the folder structure. Your landing page lives at `app/page.tsx`, pricing at `app/pricing/page.tsx`, and your authenticated product under `app/dashboard/`. The root `app/layout.tsx` holds your global navbar and the HTML shell. Create a nested `app/dashboard/layout.tsx` for the sidebar and app chrome that should appear only inside the product — not on your marketing pages. Keep components, lib, and store folders at the project root, outside `/app`, so Next.js doesn't turn them into routes.
Where does my backend logic live in an MVP?
Inside `app/api/`. Each endpoint is a folder containing a `route.ts` file that exports named async functions for HTTP methods — GET, POST, PUT, DELETE. Return JSON with `NextResponse.json()` and read POST bodies with `await request.json()`. For an MVP this replaces a separate backend service: your billing webhook, user settings update, and data queries can all live as route.ts files. Store secrets and your base URL in `.env.local`, which is git-ignored by default.
How do I keep my dashboard fast?
Load data in async server components. A dashboard page can be a server component that awaits your database query directly — no useEffect, no client-side loading spinner. Add a `loading.tsx` in the dashboard route so users see instant feedback while data resolves. For interactive pieces like settings forms or filters, extract them into `'use client'` components and import them into the server page. This server-shell, client-island pattern keeps your JavaScript bundle small and your initial loads fast, which matters for perceived product quality.
How do I handle a settings form with validation?
Keep `app/dashboard/settings/page.tsx` as a server component that fetches the user's current settings. Extract the form into `components/SettingsForm.tsx` with `'use client'` at the top so you can use useState for fields and onChange for validation. Import it into the server page and pass current settings as props. Because the form runs in the browser, its POST to `/api/settings` can use a relative path — no NEXT_URL needed. If the server page ever fetches your own API, remember it needs the absolute URL.
Do marketing pages need special treatment for SEO?
Yes — this is where you win organic traffic. Export a `metadata` constant from each marketing `page.tsx` (a server component) with a page-specific title, description, and keywords array. Add `openGraph` and `twitter` fields so shared links render rich previews. Root layout metadata provides defaults; page-level metadata overrides them. Because server components render metadata into the HTML head at request time, crawlers see it immediately. Client components can't export metadata, so keep marketing pages server-rendered.
What common mistakes slow down MVP builders?
The expensive ones: fetching your own API with a relative path in a server component (use the absolute NEXT_URL), forgetting to whitelist your CDN or image host in `next.config.ts` remotePatterns (the Image component silently fails), exporting metadata from a client component (silent SEO failure), and not handling empty API responses — call `notFound()` when data is null so a missing record doesn't crash the page. Also use the `Link` component for internal links to avoid slow full-page reloads.
Next step: Scaffold your MVP with `npx create-next-app`, create your marketing routes with metadata exports, wire one API endpoint in `app/api/`, and build your dashboard behind a nested layout. Ship the smallest working slice, then iterate route by route.
// FREQUENTLY ASKED QUESTIONS
Do I need a separate backend for my Next.js SaaS MVP?
Not for most MVPs. Next.js API routes in app/api with route.ts files give you real HTTP endpoints for form submissions, webhooks, and data mutations, running in the same project. You can add a dedicated backend later if you outgrow it, but starting with API routes lets you ship faster with one codebase and one deployment.
How do I keep the dashboard separate from marketing pages?
Use a nested layout. Put your dashboard sidebar and app chrome in app/dashboard/layout.tsx so it wraps only dashboard routes, while the root app/layout.tsx handles the global marketing navbar. This scopes shared UI cleanly without conditionally rendering it, and it keeps your marketing and product surfaces visually and structurally distinct.
Where do I store API keys and secrets in a Next.js project?
In a .env.local file at the project root, which Next.js git-ignores by default. Reference values with process.env, for example process.env.NEXT_URL for your base URL when fetching your own API from server components. Keep secret keys server-side only — never expose them in client components, which ship to the browser.