This section presents common enterprise scenarios formatted as Jira tickets. Each case includes the business requirement, technical constraints, and a senior-level implementation strategy.
We are currently using a client-side `onSubmit` handler that calls an API route `/api/contact`. This requires hydration and JavaScript to work. We want to use Next.js Server Actions to make it work progressively enhanced (even without JS).
- Remove `/api/contact` route.
- Form must work with JavaScript disabled.
- Show validation errors from the server.
👨💻 Senior Developer Solution
Strategy: Define a Server Action in a separate file and bind it to the form's `action` prop.
// actions.ts
'use server'
import { z } from 'zod';
const schema = z.object({ email: z.string().email() });
export async function submitContact(prevState: any, formData: FormData) {
const validated = schema.safeParse({ email: formData.get('email') });
if (!validated.success) {
return { message: 'Invalid email' };
}
// Save to DB...
return { message: 'Success!' };
}
// ContactForm.tsx
'use client'
import { useFormState } from 'react-dom';
import { submitContact } from './actions';
export function ContactForm() {
const [state, formAction] = useFormState(submitContact, null);
return (
<form action={formAction}>
<input name="email" type="email" />
<button type="submit">Send</button>
{state?.message && <p>{state.message}</p>}
</form>
);
}
Key Takeaway: Server Actions simplify data mutation by removing the need for manual API endpoints and `fetch` calls.
The dashboard page waits for 3 slow API calls (User, Analytics, Recent Activity) to finish before showing anything. The user sees a blank white screen for 4 seconds.
- Show the page shell (header/sidebar) immediately.
- Stream in the slow components as they finish loading.
- Show skeletons while loading.
👨💻 Senior Developer Solution
Strategy: Use React Suspense boundaries around the slow async components.
// page.tsx
import { Suspense } from 'react';
import Analytics from './Analytics';
import RecentActivity from './RecentActivity';
import Skeleton from './Skeleton';
export default function Dashboard() {
return (
<main>
<h1>Dashboard</h1>
<div className="grid">
<Suspense fallback={<Skeleton />}>
<Analytics />
</Suspense>
<Suspense fallback={<Skeleton />}>
<RecentActivity />
</Suspense>
</div>
</main>
);
}
Key Takeaway: Suspense allows the server to send the HTML shell first, then stream the rest of the HTML as data becomes available.
When users share a blog post on Twitter/LinkedIn, we want the image to dynamically show the post title and author, not just a generic logo.
- Create `opengraph-image.tsx` for blog posts.
- Image must render the post title dynamically.
👨💻 Senior Developer Solution
Strategy: Use Next.js `ImageResponse` API to generate images on the fly using JSX.
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
export default async function Image({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return new ImageResponse(
(
<div style={{
fontSize: 60,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'black',
}}>
{post.title}
</div>
),
{ width: 1200, height: 630 }
);
}
Key Takeaway: Next.js can generate images using HTML/CSS syntax, making dynamic social cards trivial.