Unverified 提交 35f5ee4d authored 作者: Mohamed Aziz Mejri's avatar Mohamed Aziz Mejri 提交者: GitHub

Plan: Nitro Server Layer Support for Vite Projects (#3090)

This plan solves the React/Vite + Neon integration issue by adding a server layer to the app using Nitro. - Nitro deploys cleanly to Vercel - API routes are automatically exposed as Vercel Functions - it keeps the setup lightweight and requires minimal effort from both the user and the AI agent <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/3090" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end -->
上级 f2265f83
# Nitro Server Layer Support for Vite Projects
> Generated by swarm planning session on 2026-03-27
## Summary
Add an opt-in server layer (Nitro) to Vite/React apps in Dyad, giving them filesystem-based API routes that deploy as Vercel Functions. This closes the architectural gap between Next.js (which has built-in API routes) and Vite for database-backed apps — specifically unblocking Neon database integration for Vite projects, which requires server-side access to `DATABASE_URL`.
## Problem Statement
Dyad users building React/Vite apps have no server layer. This creates three concrete problems:
1. **No secure secrets management** — Vite apps can't safely hold `DATABASE_URL` or other server-side credentials. The AI agent cannot generate server-side database code for Vite apps.
2. **Neon integration is blocked for Vite** — Neon's standard connection pattern uses `DATABASE_URL` with `@neondatabase/serverless`, which requires server-side code. The previously proposed workaround (Neon Data API + RLS) is non-standard and may require extra configuration from the users.
3. **No Vercel Functions** — Without a server layer, Vite apps deployed to Vercel are static-only. Users who need webhook handlers, API proxies, or any server logic must switch to Next.js.
Since Vite is the default template, this affects the majority of Dyad apps.
## Scope
### In Scope (MVP — This PR)
- **Nitro toggle on app-details page** — A one-directional switch (enable only) with confirmation dialog, shown only for Vite/React apps
- **Hybrid scaffolding** — Programmatically add `nitro` to `package.json` and create `server/routes/api/` directory; AI agent handles `vite.config.ts` modification via system prompt
- **System prompt**`nitro_prompt.ts` with Nitro-specific coding patterns (filesystem routing, `defineHandler`, `useRuntimeConfig()`, `server/routes/api/` convention)
- **Vercel compatibility** — Nitro's Vercel preset (`preset: "vercel"`) generates `.vercel/output/` at build time, which Vercel understands natively
- **Integration test** — End-to-end: create Vite app → enable Nitro → chat to generate route → deploy to Vercel → verify API route responds
### Out of Scope (Follow-up)
- Making Nitro mandatory for Neon users (follow-up PR — auto-enable Nitro when Neon is connected to a Vite app)
- Neon-specific agent tools or system prompts (separate Neon integration work)
- Supabase-specific Nitro patterns (Supabase has its own Edge Functions)
- Disabling Nitro from UI (v1 is enable-only; removal via AI assistant in chat)
- `vercel.json` modification (deferred — Nitro's Build Output API takes precedence)
- Multi-platform deployment presets (MVP hardcodes Vercel preset)
- Modifying the default scaffold template (Nitro is added dynamically, not baked in)
## User Stories
1. **As a Vite app developer who wants server-side logic**, I want to enable backend API routes by flipping a switch and confirming, then have the AI assistant complete the Vite config setup on my next chat — so that I get a working server layer without manually configuring build tools.
2. **As a Vite app developer deploying to Vercel**, I want my API routes to automatically become Vercel Functions — so that deployment just works without extra configuration.
3. **As a Vite+Supabase developer**, I want the option to enable a server layer for custom API routes if I need it — but I don't want it forced on me since Supabase handles most server-side needs.
4. **As a user who enabled Nitro and wants to remove it**, I can ask the AI assistant to undo the setup in chat — so I'm not stuck with an irreversible action, even though the uncommon path is handled conversationally rather than with dedicated UI.
5. **(Follow-up) As a Vite+Neon developer**, I want the system to automatically enable the server layer when I connect Neon — so the AI agent can generate secure server-side database queries using `DATABASE_URL`.
## UX Design
### User Flow
1. User navigates to App Details page for their Vite project
2. User sees **"Server Layer"** card in the integrations section (between Supabase and Vercel connectors)
3. Card shows a Switch (OFF) with label **"Enable backend API routes"** and subtitle _"Powered by Nitro"_
4. User flips switch ON
5. **Confirmation dialog appears:**
- Title: "Enable Server Layer?"
- Body: "This will enable backend API routes for your app, adding dependencies and creating a server/routes/api/ directory. Your Vite config will be updated by the AI assistant on your next chat message."
- Buttons: "Cancel" / "Enable"
6. On confirm → scaffolding runs (package.json modification, directory creation, pnpm install)
7. Switch shows loading state (disabled + Loader2 spinner, "Setting up...")
8. On success → toast: "Server layer enabled"
9. Card updates to enabled state with nudge: **"Open chat to finish setup"** button + tip: _"Try asking: 'Create a /api/users endpoint'"_
10. User opens chat → AI agent detects Nitro enabled but `vite.config.ts` not configured → agent proactively adds Nitro plugin to vite.config.ts
11. Server layer is fully operational
### Key States
| State | Visual | Switch | Description |
| ------------ | ------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **OFF** | Card with description | Toggleable | "Add server-side API routes to your project." |
| **ENABLING** | Loader2 spinner | Disabled | "Setting up..." — scaffolding in progress |
| **ON** | Green "Active" badge | ON + disabled (permanent) | Shows "Open chat to finish setup" nudge, tip for first API route. Muted note: "To remove, ask the AI assistant in chat." |
| **ERROR** | Red alert + Retry button | Reverts to OFF | Error message with actionable retry CTA |
The **LOCKED_ON** state (Neon requires Nitro) is deferred to the follow-up PR when Neon auto-enable lands.
### Interaction Details
- **Confirmation dialog**: Follows the existing Rename dialog pattern (`DialogContent max-w-sm` with `DialogHeader`, `DialogDescription`, `DialogFooter` with Cancel/Confirm buttons)
- **Switch component**: Uses existing `Switch` from `src/components/ui/switch.tsx`, consistent with ~10 existing switches in codebase
- **One-directional**: Once ON, the switch is `disabled` — no off affordance in UI. Note: "To remove, ask the AI assistant in chat"
- **Toast**: Success/error feedback via sonner (consistent with SupabaseConnector)
- **"Open chat" button**: Secondary button in the Card that navigates to chat, similar to existing "Open in Chat" pattern
### Placement
On the app-details page, positioned between SupabaseConnector and CapacitorControls:
```
GitHub Connector
Supabase Connector
→ Server Layer (Nitro) ← NEW
Capacitor Controls
```
Only rendered for Vite/React template apps (not Next.js, which already has a server layer).
### Accessibility
- Switch: `aria-label="Enable backend API routes"`
- Disabled state (when ON): `aria-describedby` pointing to explanation text ("To remove, ask the AI assistant")
- Loading state: `aria-busy="true"` on the Card
- Status badge: text label alongside color (not color-only)
- All interactive elements keyboard-focusable
- Loading spinner respects `prefers-reduced-motion`
## Technical Design
### Architecture
**Nitro-enabled Vite app structure:**
```
┌──────────────────────────────────────────┐
│ React SPA (client) │
│ fetch("/api/todos") → Nitro route │
├──────────────────────────────────────────┤
│ Nitro Server Layer │
│ server/routes/api/*.ts │
│ defineHandler from "nitro" │
│ useRuntimeConfig() for env vars │
├──────────────────────────────────────────┤
│ Vercel Functions (production) │
│ .vercel/output/ (Build Output API v3) │
└──────────────────────────────────────────┘
```
**Hybrid setup approach:**
- **Programmatic** (IPC handler): `package.json` modification, `server/routes/api/` directory creation
- **AI agent** (system prompt): `vite.config.ts` modification (adding Nitro plugin import + config)
- **Deferred**: `vercel.json` — Nitro's Vercel preset generates `.vercel/output/` which takes precedence
### Components Affected
| Component | File(s) | Change |
| -------------------- | ------------------------------------------ | --------------------------------------------------------------------------- |
| **DB Schema** | `src/db/schema.ts` | Add `nitroEnabled` boolean column to `apps` table |
| **IPC Handler** | `src/ipc/handlers/nitro_handlers.ts` (new) | `app:set-nitro-enabled` — modify package.json, create server dir, update DB |
| **IPC Types** | `src/ipc/types/nitro.ts` (new) | Contract schema for `app:set-nitro-enabled` |
| **App Details Page** | `src/pages/app-details.tsx` | Add NitroToggle component (Vite apps only) |
| **Nitro Toggle** | `src/components/NitroToggle.tsx` (new) | Card + Switch + confirmation dialog + states |
| **System Prompt** | `src/prompts/nitro_prompt.ts` (new) | Nitro setup instructions + API route patterns |
| **Chat Stream** | `src/ipc/handlers/chat_stream_handlers.ts` | Inject Nitro prompt when `nitroEnabled === true` |
### Data Model Changes
**New column on `apps` table:**
```typescript
nitroEnabled: integer("nitro_enabled", { mode: "boolean" })
.notNull()
.default(sql`0`);
```
Simple boolean, backward-compatible. Defaults to false.
### API Changes
**New IPC contract:**
| Contract | Input | Output |
| ----------------------- | ---------------------------------- | ---------------------- |
| `app:set-nitro-enabled` | `{ appId: number, enabled: true }` | `{ success: boolean }` |
**Handler logic (enable only):**
1. Read app's `package.json` → add `nitro` (pinned version) to dependencies → write back
2. Create `server/routes/api/.gitkeep`
3. Set `nitroEnabled = true` in DB (commit step — only after file ops succeed)
4. Trigger install (hook into existing install mechanism via `installCommand`)
**Rollback on failure:** If any file operation fails, revert previously written files (read originals into memory before mutations). DB write is last, so `nitroEnabled` is never true if file setup failed.
### System Prompt Design (`nitro_prompt.ts`)
```
## Nitro Server Layer
The user has enabled backend API routes (Nitro) for this project.
### Setup Check
Check if `vite.config.ts` imports and uses the Nitro plugin.
If NOT present, update vite.config.ts to include:
1. Add import: `import { nitro } from "nitro/vite";`
2. Add `nitro({ preset: "vercel" })` to the plugins array
Example vite.config.ts:
[full example with nitro plugin in correct position]
### API Route Conventions
- Write routes in `server/routes/api/` (NEVER top-level `/api/`)
- Use `defineHandler` from "nitro" for handlers
- Dynamic routes: `[param].ts`
- Method-specific: `hello.get.ts`, `hello.post.ts`
- Runtime config: `useRuntimeConfig()` (env vars prefixed with `NITRO_`)
### Security Rules
NEVER import server-side code (database clients, secrets, env vars)
in client-side React components. Server code lives in `server/` only.
```
**Critical**: The prompt must detect the half-configured state (Nitro enabled in DB but plugin not in vite.config.ts) and proactively complete the setup on first chat interaction.
### Vercel Compatibility Strategy
- System prompt hardcodes `nitro({ preset: "vercel" })` in the plugin config
- At build time, Nitro produces `.vercel/output/` (Build Output API v3) which Vercel understands natively
- The existing `vercel.json` SPA rewrite should be harmless because Build Output API takes precedence when `.vercel/output/` exists
- `detectFramework()` in `vercel_handlers.ts` returns `"vite"` (correct — Vercel recognizes Vite+Nitro)
- **Fallback**: If integration test reveals `vercel.json` interferes, add vercel.json deletion/modification to the toggle handler
## Implementation Plan
### Phase 1: DB + IPC (Foundation)
- [ ] Add `nitroEnabled` column to `apps` table in `src/db/schema.ts`
- [ ] Create `src/ipc/types/nitro.ts` with contract schema
- [ ] Create `src/ipc/handlers/nitro_handlers.ts` with `app:set-nitro-enabled` handler
- [ ] Handler: read/modify package.json, create server/routes/api/, write DB, trigger install
- [ ] Implement rollback logic (revert file changes if any step fails)
### Phase 2: UI
- [ ] Create `src/components/NitroToggle.tsx` — Card with Switch, confirmation dialog, loading/error/enabled states
- [ ] Integrate NitroToggle into `src/pages/app-details.tsx` (between Supabase and Vercel connectors, Vite apps only)
- [ ] One-directional: switch is `disabled` once ON, with "To remove, ask AI" note
- [ ] "Open chat to finish setup" nudge button in enabled state
### Phase 3: System Prompt
- [ ] Create `src/prompts/nitro_prompt.ts` with vite.config.ts setup check + API route conventions + security rules
- [ ] Integrate into `src/ipc/handlers/chat_stream_handlers.ts` — inject when `nitroEnabled === true`
- [ ] Ensure prompt detects half-configured state and proactively completes vite.config.ts setup
### Phase 4: Verification
- [ ] Integration test: create Vite app → enable Nitro → chat to trigger vite.config.ts setup → generate sample API route → `vite build` produces `.vercel/output/`
- [ ] Integration test: deploy Nitro-enabled Vite app to Vercel → verify API route responds
- [ ] Verify `vercel.json` catch-all rewrite doesn't interfere with Nitro's Build Output API
- [ ] Verify Vite 6.x + `nitro/vite` compatibility
- [ ] Verify `dyadComponentTagger` plugin doesn't conflict with Nitro plugin
- [ ] Verify dev server serves both React app and API routes on same port (8080)
- [ ] Regression test: existing Vite apps without Nitro still work unchanged
### Phase 5: Follow-up PR (Neon Auto-Enable)
- [ ] Auto-enable Nitro when Neon is connected to a Vite app
- [ ] Add LOCKED_ON state to NitroToggle (switch disabled with "Required by Neon" tooltip)
- [ ] Update Neon system prompt to generate `DATABASE_URL`-based server routes via Nitro
- [ ] Update `plans/neondb-integration.md` to replace Data API approach with Nitro for Vite apps
- [ ] Per-app Neon connector on app-details page (must ship alongside locked state)
## Testing Strategy
- [ ] Unit test: `app:set-nitro-enabled` IPC handler — verify package.json modification, directory creation, DB state
- [ ] Unit test: rollback on partial failure — simulate file write failure, verify clean state
- [ ] Unit test: NitroToggle component — confirmation dialog, loading states, one-directional behavior
- [ ] Integration test: full Nitro scaffolding + AI config + build + deploy pipeline
- [ ] Integration test: dev server with Nitro — API routes accessible at `localhost:8080/api/*`
- [ ] Regression test: Vite app without Nitro — toggle not shown for Next.js apps, existing apps unaffected
- [ ] System prompt test: AI generates correct `server/routes/api/*.ts` with `defineHandler` pattern
## Risks & Mitigations
| Risk | Likelihood | Impact | Mitigation |
| ------------------------------------------------------------------------ | ---------- | ------ | -------------------------------------------------------------------------------------- |
| `vercel.json` catch-all rewrite interferes with Nitro's Build Output API | Medium | High | Integration test verifies; fallback: delete/modify vercel.json in toggle handler |
| Vite 6.x incompatible with `nitro/vite` plugin | Low | High | Pin compatible Nitro version; verify in integration test |
| AI agent fails to modify vite.config.ts correctly | Medium | Medium | System prompt includes exact example; half-configured state is recoverable via re-chat |
| pnpm install not triggered after package.json modification | Medium | Medium | Hook into existing install lifecycle; toast instructs user if needed |
| `dyadComponentTagger` plugin conflicts with Nitro plugin | Low | Low | Test plugin ordering; Nitro should be first in plugins array |
| Dev server port conflict (Nitro vs Vite port 8080) | Low | Low | `nitro/vite` integrates into Vite's dev middleware; verify in integration test |
| Half-configured state confuses users | Medium | Medium | Card shows "Open chat to finish setup" nudge; prompt auto-completes config |
| Nitro version drift causes breaking changes | Low | Medium | Pin specific tested version in package.json modification |
## Open Questions
- **Nitro version**: Which specific version to pin? Must be tested against Vite 6.x and the Vercel preset. Determine during Phase 1.
- **TypeScript types for server/**: Does the `nitro` package auto-generate types in `.nitro/`? If not, may need `server/tsconfig.json`. Verify during Phase 4.
- **`DATABASE_URL` injection during local dev** (follow-up): When Neon is connected, how does the dev server get the connection string? Options: Dyad writes `.env` file, Dyad sets env var on process, or system prompt instructs agent. Resolve before Phase 5.
## Decision Log
| Decision | Reasoning |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Hybrid setup** (programmatic JSON + AI vite.config.ts) | JSON files are trivially safe to modify programmatically; TypeScript config is fragile and better handled contextually by AI agent. Mirrors Supabase client file pattern. |
| **One-directional toggle (enable only, v1)** | Disabling requires file cleanup with no codebase precedent. Avoids destructive edge cases. Removal via AI assistant is sufficient escape hatch. |
| **Confirmation dialog on enable** | Toggle modifies project files — heavier than a preference switch. Transparency principle requires user consent for file changes. |
| **"Server Layer" label, not "Nitro"** | Users think in capabilities ("I need API routes"), not tools ("I need Nitro"). "Powered by Nitro" as subtitle for power users. |
| **Show on ALL Vite apps** | Nitro is useful beyond databases (webhooks, API proxies, server logic). Gating behind database connection would artificially limit a general-purpose capability. |
| **Standalone card, not nested under DB connector** | Nitro is a server layer feature, not a database feature. Avoids false dependency. Follow-up PR adds visual link ("Required by Neon" badge). |
| **`nitroEnabled` boolean, not enum** | YAGNI. Nitro is the only server layer for Vite. Migration from boolean to enum is trivial if ever needed. |
| **Defer vercel.json modification** | Nitro's Vercel preset generates `.vercel/output/` which Build Output API understands natively. No need to touch vercel.json unless proven necessary. |
| **Replace Data API approach in Neon plan** | Nitro gives Vite apps conventional server-side access identical to Next.js. Data API + RLS is non-standard and harder for the AI agent to reason about. Update `neondb-integration.md` in follow-up. |
---
_Generated by dyad:swarm-to-plan_
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论