Pricing Frontend: Next.js 14 Architecture Deep Dive
Executive Summary
The pricing-frontend is a modern, serverless web application built with Next.js 14, React 18, and TypeScript that runs on Node.js runtime and is deployed using the SST (Serverless Stack) framework. This document provides a comprehensive technical explanation of how Next.js, React, and TypeScript are used throughout the architecture, from server-side rendering to real-time GraphQL data fetching and role-based access control.
Table of Contents
- Next.js 14 and React 18 Foundation
- TypeScript in Next.js
- Server-Side Rendering (SSR) Architecture
- State Management with Jotai and React Query
- GraphQL Integration
- Material-UI Component Library
- Authentication and Authorization
- SST Serverless Deployment
- Code Structure and Patterns
- Performance Optimization
- Development Workflow
Next.js 14 and React 18 Foundation
What is Next.js 14 in This Context?
Next.js 14 is a React framework built on Node.js that provides:
- Server-Side Rendering (SSR) for fast initial page loads
- App Router for modern routing and layouts
- API Routes for backend functionality
- Automatic code splitting for optimal performance
- Built-in optimizations for images, fonts, and scripts
- Serverless deployment support via SST
Why Next.js 14 for Pricing Frontend?
- Server-Side Rendering: Fast initial page loads for pricing staff
- Real-Time Updates: React 18's concurrent features enable smooth UI updates
- TypeScript Native: First-class TypeScript support
- Serverless Ready: SST framework integrates seamlessly with Next.js
- Performance: Optimized for fast loading times
- Business Impact: Enabled real-time franchisee pricing for 8,000+ locations
Runtime Environment
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā AWS Lambda Execution Environment ā
ā (via SST Framework) ā
ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
ā ā Node.js Runtime (18.x) ā ā
ā ā ā ā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāā ā ā
ā ā ā Next.js 14 Framework ā ā ā
ā ā ā ā ā ā
ā ā ā āāāāāāāāāāāāāāāāāāāāā ā ā ā
ā ā ā ā React 18 ā ā ā ā
ā ā ā ā (Compiled JS) ā ā ā ā
ā ā ā āāāāāāāāāāāāāāāāāāāāā ā ā ā
ā ā ā āāāāāāāāāāāāāāāāāāāāā ā ā ā
ā ā ā ā TypeScript ā ā ā ā
ā ā ā ā (Compiled to JS) ā ā ā ā
ā ā ā āāāāāāāāāāāāāāāāāāāāā ā ā ā
ā ā āāāāāāāāāāāāāāāāāāāāāāāāā ā ā
ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
TypeScript in Next.js
TypeScript Configuration
The pricing-frontend uses TypeScript throughout for type safety:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
TypeScript Component Example
// app/pricing/page.tsx
import { Metadata } from 'next';
import { PricingDashboard } from '@/components/pricing/PricingDashboard';
import { getPricingData } from '@/lib/graphql/queries';
export const metadata: Metadata = {
title: 'Pricing Management',
description: 'Manage pricing for all store locations',
};
interface PricingPageProps {
searchParams: {
storeId?: string;
page?: string;
};
}
export default async function PricingPage({ searchParams }: PricingPageProps) {
// Server-side data fetching with TypeScript
const storeId = searchParams.storeId;
const page = parseInt(searchParams.page || '1', 10);
const pricingData = await getPricingData({
storeId,
page,
limit: 50,
});
return (
<PricingDashboard
initialData={pricingData}
storeId={storeId}
/>
);
}
Server-Side Rendering (SSR) Architecture
Next.js App Router Structure
The pricing-frontend uses Next.js 14 App Router for modern routing:
app/
āāā layout.tsx # Root layout (SSR)
āāā page.tsx # Home page
āāā pricing/
ā āāā layout.tsx # Pricing section layout
ā āāā page.tsx # Pricing dashboard (SSR)
ā āāā [storeId]/
ā ā āāā page.tsx # Store-specific pricing (SSR)
ā āāā reports/
ā āāā page.tsx # Pricing reports (SSR)
āāā api/
ā āāā auth/
ā āāā [...nextauth]/
ā āāā route.ts # NextAuth API route
āāā globals.css
Server Component Example
// app/pricing/page.tsx
import { Suspense } from 'react';
import { PricingTable } from '@/components/pricing/PricingTable';
import { PricingFilters } from '@/components/pricing/PricingFilters';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
// This is a Server Component (runs on Node.js)
export default async function PricingPage() {
// Server-side authentication check
const session = await getServerSession(authOptions);
if (!session) {
redirect('/login');
}
// Server-side data fetching
const initialPricingData = await fetchPricingData(session.user.storeIds);
return (
<div className="container mx-auto p-6">
<h1 className="text-2xl font-bold mb-4">Pricing Management</h1>
<Suspense fallback={<PricingFiltersSkeleton />}>
<PricingFilters />
</Suspense>
<Suspense fallback={<PricingTableSkeleton />}>
<PricingTable initialData={initialPricingData} />
</Suspense>
</div>
);
}
SSR Data Fetching
// lib/graphql/server-queries.ts
import { getServerSession } from 'next-auth';
import { graphqlClient } from './client';
export async function fetchPricingData(storeIds: string[]) {
const session = await getServerSession(authOptions);
// Server-side GraphQL query
const query = `
query GetPricingData($storeIds: [ID!]!) {
prices(storeIds: $storeIds) {
id
storeId
productId
amount
currency
effectiveDate
expirationDate
}
}
`;
const response = await graphqlClient.request(query, {
storeIds,
headers: {
Authorization: `Bearer ${session?.accessToken}`,
},
});
return response.prices;
}
SSR Execution Flow
1. User requests /pricing
ā
2. Next.js server (Node.js) receives request
ā
3. Server Component executes
- Runs getServerSession() (server-side)
- Runs fetchPricingData() (server-side)
- Renders React components to HTML
ā
4. HTML sent to client (with data)
ā
5. Client hydrates React components
ā
6. Interactive features enabled
State Management with Jotai and React Query
Jotai for UI State
Jotai is used for lightweight, atomic state management:
// atoms/pricing-atoms.ts
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
// UI state atoms
export const selectedStoreIdAtom = atom<string | null>(null);
export const filterAtom = atom({
search: '',
category: 'all',
dateRange: '30days',
});
// Persisted state (localStorage)
export const userPreferencesAtom = atomWithStorage('pricing-preferences', {
pageSize: 50,
defaultView: 'table',
showInactive: false,
});
// Derived atoms
export const filteredPricesAtom = atom((get) => {
const prices = get(pricesAtom);
const filter = get(filterAtom);
return prices.filter(price => {
if (filter.search && !price.productName.includes(filter.search)) {
return false;
}
if (filter.category !== 'all' && price.category !== filter.category) {
return false;
}
return true;
});
});
React Query for Server State
React Query manages server state, caching, and synchronization:
// hooks/use-pricing-data.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { graphqlClient } from '@/lib/graphql/client';
export function usePricingData(storeId: string | null) {
return useQuery({
queryKey: ['pricing', storeId],
queryFn: async () => {
if (!storeId) return null;
const query = `
query GetPricing($storeId: ID!) {
prices(storeId: $storeId) {
id
storeId
productId
amount
currency
effectiveDate
}
}
`;
const response = await graphqlClient.request(query, { storeId });
return response.prices;
},
enabled: !!storeId,
staleTime: 30000, // 30 seconds
refetchOnWindowFocus: true,
});
}
export function useUpdatePrice() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (input: UpdatePriceInput) => {
const mutation = `
mutation UpdatePrice($input: UpdatePriceInput!) {
updatePrice(input: $input) {
id
amount
effectiveDate
}
}
`;
return await graphqlClient.request(mutation, { input });
},
onSuccess: (data, variables) => {
// Invalidate and refetch pricing data
queryClient.invalidateQueries({ queryKey: ['pricing', variables.storeId] });
},
});
}
Combined State Management
// components/pricing/PricingDashboard.tsx
'use client';
import { useAtom } from 'jotai';
import { usePricingData, useUpdatePrice } from '@/hooks/use-pricing-data';
import { selectedStoreIdAtom, filterAtom } from '@/atoms/pricing-atoms';
export function PricingDashboard() {
const [selectedStoreId] = useAtom(selectedStoreIdAtom);
const [filter, setFilter] = useAtom(filterAtom);
// React Query for server state
const { data: pricingData, isLoading, error } = usePricingData(selectedStoreId);
const updatePriceMutation = useUpdatePrice();
const handlePriceUpdate = async (priceId: string, newAmount: number) => {
await updatePriceMutation.mutateAsync({
id: priceId,
amount: newAmount,
storeId: selectedStoreId!,
});
};
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div>
<PricingFilters filter={filter} onFilterChange={setFilter} />
<PricingTable
data={pricingData}
onPriceUpdate={handlePriceUpdate}
/>
</div>
);
}
GraphQL Integration
GraphQL Client Setup
// lib/graphql/client.ts
import { GraphQLClient } from 'graphql-request';
import { getSession } from 'next-auth/react';
const graphqlEndpoint = process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT!;
export const graphqlClient = new GraphQLClient(graphqlEndpoint, {
credentials: 'include',
requestMiddleware: async (request) => {
const session = await getSession();
if (session?.accessToken) {
request.headers = {
...request.headers,
Authorization: `Bearer ${session.accessToken}`,
};
}
return request;
},
});
GraphQL Queries
// lib/graphql/queries.ts
import { graphqlClient } from './client';
import { gql } from 'graphql-request';
export const GET_PRICES_QUERY = gql`
query GetPrices($storeId: ID!, $filter: PriceFilter) {
prices(storeId: $storeId, filter: $filter) {
id
storeId
productId
productName
amount
currency
effectiveDate
expirationDate
category
}
}
`;
export const UPDATE_PRICE_MUTATION = gql`
mutation UpdatePrice($input: UpdatePriceInput!) {
updatePrice(input: $input) {
id
amount
effectiveDate
expirationDate
}
}
`;
export async function getPricingData(variables: {
storeId: string;
filter?: PriceFilter;
}) {
return await graphqlClient.request(GET_PRICES_QUERY, variables);
}
Real-Time Data Synchronization
// hooks/use-realtime-pricing.ts
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { graphqlClient } from '@/lib/graphql/client';
export function useRealtimePricing(storeId: string) {
const queryClient = useQueryClient();
useEffect(() => {
if (!storeId) return;
// GraphQL subscription for real-time updates
const subscription = graphqlClient.subscribe(
`
subscription OnPriceUpdate($storeId: ID!) {
onPriceUpdate(storeId: $storeId) {
id
amount
effectiveDate
}
}
`,
{ storeId },
{
next: (data) => {
// Update React Query cache with new data
queryClient.setQueryData(['pricing', storeId], (oldData: any) => {
if (!oldData) return oldData;
return oldData.map((price: any) =>
price.id === data.onPriceUpdate.id
? { ...price, ...data.onPriceUpdate }
: price
);
});
},
error: (error) => {
console.error('Subscription error:', error);
},
}
);
return () => {
subscription.unsubscribe();
};
}, [storeId, queryClient]);
}
Material-UI Component Library
Material-UI Setup
// app/layout.tsx
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { theme } from '@/lib/theme';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</body>
</html>
);
}
Material-UI Components
// components/pricing/PricingTable.tsx
'use client';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
TextField,
Button,
Chip,
} from '@mui/material';
import { Edit, Save, Cancel } from '@mui/icons-material';
interface PricingTableProps {
data: Price[];
onPriceUpdate: (priceId: string, amount: number) => Promise<void>;
}
export function PricingTable({ data, onPriceUpdate }: PricingTableProps) {
const [editingId, setEditingId] = useState<string | null>(null);
const [editValue, setEditValue] = useState<string>('');
return (
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>Product</TableCell>
<TableCell>Current Price</TableCell>
<TableCell>Effective Date</TableCell>
<TableCell>Status</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((price) => (
<TableRow key={price.id}>
<TableCell>{price.productName}</TableCell>
<TableCell>
{editingId === price.id ? (
<TextField
type="number"
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
size="small"
/>
) : (
`$${price.amount.toFixed(2)}`
)}
</TableCell>
<TableCell>{formatDate(price.effectiveDate)}</TableCell>
<TableCell>
<Chip
label={price.isActive ? 'Active' : 'Inactive'}
color={price.isActive ? 'success' : 'default'}
size="small"
/>
</TableCell>
<TableCell>
{editingId === price.id ? (
<>
<Button
size="small"
startIcon={<Save />}
onClick={async () => {
await onPriceUpdate(price.id, parseFloat(editValue));
setEditingId(null);
}}
>
Save
</Button>
<Button
size="small"
startIcon={<Cancel />}
onClick={() => setEditingId(null)}
>
Cancel
</Button>
</>
) : (
<Button
size="small"
startIcon={<Edit />}
onClick={() => {
setEditingId(price.id);
setEditValue(price.amount.toString());
}}
>
Edit
</Button>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
Authentication and Authorization
NextAuth.js Configuration
// lib/auth.ts
import NextAuth, { NextAuthOptions } from 'next-auth';
import CognitoProvider from 'next-auth/providers/cognito';
export const authOptions: NextAuthOptions = {
providers: [
CognitoProvider({
clientId: process.env.COGNITO_CLIENT_ID!,
clientSecret: process.env.COGNITO_CLIENT_SECRET!,
issuer: process.env.COGNITO_ISSUER!,
}),
],
callbacks: {
async jwt({ token, user, account }) {
if (account) {
token.accessToken = account.access_token;
token.idToken = account.id_token;
}
return token;
},
async session({ session, token }) {
session.accessToken = token.accessToken;
session.user.storeIds = token.storeIds;
session.user.role = token.role;
return session;
},
},
pages: {
signIn: '/login',
},
};
export default NextAuth(authOptions);
Role-Based Access Control
// middleware.ts
import { withAuth } from 'next-auth/middleware';
import { NextResponse } from 'next/server';
export default withAuth(
function middleware(req) {
const token = req.nextauth.token;
const path = req.nextUrl.pathname;
// Role-based route protection
if (path.startsWith('/pricing/admin') && token?.role !== 'admin') {
return NextResponse.redirect(new URL('/pricing', req.url));
}
if (path.startsWith('/pricing') && !token?.storeIds?.length) {
return NextResponse.redirect(new URL('/unauthorized', req.url));
}
return NextResponse.next();
},
{
callbacks: {
authorized: ({ token }) => !!token,
},
}
);
export const config = {
matcher: ['/pricing/:path*'],
};
Protected Route Example
// app/pricing/admin/page.tsx
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { redirect } from 'next/navigation';
export default async function AdminPricingPage() {
const session = await getServerSession(authOptions);
if (!session) {
redirect('/login');
}
if (session.user.role !== 'admin') {
redirect('/pricing');
}
return (
<div>
<h1>Admin Pricing Management</h1>
{/* Admin-only content */}
</div>
);
}
SST Serverless Deployment
SST Configuration
// sst.config.ts
import { NextjsSite } from 'sst/constructs';
export default {
config(_input) {
return {
name: 'pricing-frontend',
region: 'us-east-1',
};
},
stacks(app) {
app.stack(function Site({ stack }) {
const site = new NextjsSite(stack, 'PricingFrontend', {
path: '.',
environment: {
NEXT_PUBLIC_GRAPHQL_ENDPOINT: process.env.GRAPHQL_ENDPOINT!,
NEXT_PUBLIC_COGNITO_CLIENT_ID: process.env.COGNITO_CLIENT_ID!,
NEXT_PUBLIC_COGNITO_ISSUER: process.env.COGNITO_ISSUER!,
},
bind: [
// Bind AWS resources for access
pricingServiceApi,
cognitoUserPool,
],
});
stack.addOutputs({
SiteUrl: site.url,
});
});
},
};
SST Deployment Architecture
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā CloudFront CDN ā
ā - Global edge caching ā
ā - Fast content delivery ā
āāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā AWS Lambda@Edge ā
ā - Next.js SSR execution ā
ā - API routes ā
āāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā S3 Bucket ā
ā - Static assets (JS, CSS, images) ā
ā - Pre-rendered pages ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Deployment Process
# 1. Install dependencies
pnpm install
# 2. Build Next.js application
pnpm build
# 3. Deploy with SST
sst deploy
# SST automatically:
# - Builds Next.js app
# - Creates Lambda functions for SSR
# - Uploads static assets to S3
# - Configures CloudFront CDN
# - Sets up environment variables
Code Structure and Patterns
Project Structure
pricing-frontend/
āāā app/ # Next.js App Router
ā āāā layout.tsx # Root layout
ā āāā page.tsx # Home page
ā āāā pricing/ # Pricing section
ā ā āāā layout.tsx
ā ā āāā page.tsx
ā ā āāā [storeId]/
ā ā āāā page.tsx
ā āāā api/ # API routes
ā āāā auth/
ā āāā [...nextauth]/
ā āāā route.ts
āāā components/ # React components
ā āāā pricing/
ā ā āāā PricingTable.tsx
ā ā āāā PricingFilters.tsx
ā ā āāā PricingDashboard.tsx
ā āāā common/
ā āāā Button.tsx
ā āāā LoadingSpinner.tsx
āāā hooks/ # Custom React hooks
ā āāā use-pricing-data.ts
ā āāā use-realtime-pricing.ts
ā āāā use-update-price.ts
āāā atoms/ # Jotai atoms
ā āāā pricing-atoms.ts
āāā lib/ # Utilities
ā āāā graphql/
ā ā āāā client.ts
ā ā āāā queries.ts
ā āāā auth.ts
ā āāā theme.ts
āāā types/ # TypeScript types
ā āāā pricing.ts
āāā sst.config.ts # SST configuration
āāā next.config.js # Next.js configuration
āāā tsconfig.json # TypeScript configuration
āāā package.json
Next.js Patterns Used
1. Server and Client Components
// Server Component (default)
// app/pricing/page.tsx
export default async function PricingPage() {
const data = await fetchPricingData(); // Server-side
return <PricingDashboard initialData={data} />;
}
// Client Component (with 'use client')
// components/pricing/PricingDashboard.tsx
'use client';
export function PricingDashboard({ initialData }) {
const [state, setState] = useState(); // Client-side
return <div>...</div>;
}
2. Parallel Data Fetching
// app/pricing/page.tsx
export default async function PricingPage() {
// Fetch multiple data sources in parallel
const [pricingData, storeData, userData] = await Promise.all([
fetchPricingData(),
fetchStoreData(),
fetchUserData(),
]);
return (
<PricingDashboard
pricingData={pricingData}
storeData={storeData}
userData={userData}
/>
);
}
3. Streaming with Suspense
// app/pricing/page.tsx
import { Suspense } from 'react';
export default function PricingPage() {
return (
<div>
<Suspense fallback={<FiltersSkeleton />}>
<PricingFilters />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<PricingTable />
</Suspense>
</div>
);
}
4. Error Boundaries
// app/pricing/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
5. Loading States
// app/pricing/loading.tsx
export default function Loading() {
return (
<div className="flex items-center justify-center h-screen">
<LoadingSpinner />
<p>Loading pricing data...</p>
</div>
);
}
Performance Optimization
Next.js Optimizations
1. Image Optimization
// components/common/OptimizedImage.tsx
import Image from 'next/image';
export function OptimizedImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={800}
height={600}
priority // Preload important images
placeholder="blur" // Blur placeholder
/>
);
}
2. Code Splitting
// Dynamic imports for code splitting
import dynamic from 'next/dynamic';
const PricingReports = dynamic(
() => import('@/components/pricing/PricingReports'),
{
loading: () => <LoadingSpinner />,
ssr: false, // Client-only component
}
);
3. Font Optimization
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
4. Bundle Analysis
# Analyze bundle size
ANALYZE=true pnpm build
# Output shows:
# - Bundle sizes
# - Code splitting effectiveness
# - Duplicate dependencies
React Query Optimizations
// Optimized React Query configuration
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30000, // 30 seconds
cacheTime: 300000, // 5 minutes
refetchOnWindowFocus: true,
refetchOnReconnect: true,
retry: 1,
},
},
});
Fast Loading Times
Achieved through:
- Server-Side Rendering (initial HTML with data)
- Code splitting (load only needed code)
- Image optimization (Next.js Image component)
- Font optimization (next/font)
- React Query caching (reduce API calls)
- CloudFront CDN (edge caching)
- Lambda@Edge (SSR at edge locations)
Development Workflow
Local Development
# 1. Install Node.js (v18+)
node --version # v18.x.x
# 2. Install dependencies
pnpm install
# 3. Set up environment variables
cp .env.example .env.local
# Edit .env.local with your values
# 4. Run development server
pnpm dev
# Starts Next.js dev server on http://localhost:3000
# 5. Run type checking
pnpm type-check
# 6. Run linting
pnpm lint
# 7. Run tests
pnpm test
Testing Next.js Code
// __tests__/components/PricingTable.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { PricingTable } from '@/components/pricing/PricingTable';
describe('PricingTable', () => {
const mockData = [
{
id: '1',
productName: 'Taco',
amount: 1.99,
effectiveDate: '2024-01-01',
},
];
it('renders pricing data', () => {
render(<PricingTable data={mockData} onPriceUpdate={jest.fn()} />);
expect(screen.getByText('Taco')).toBeInTheDocument();
expect(screen.getByText('$1.99')).toBeInTheDocument();
});
it('allows editing prices', () => {
const onUpdate = jest.fn();
render(<PricingTable data={mockData} onPriceUpdate={onUpdate} />);
fireEvent.click(screen.getByText('Edit'));
fireEvent.change(screen.getByDisplayValue('1.99'), {
target: { value: '2.49' },
});
fireEvent.click(screen.getByText('Save'));
expect(onUpdate).toHaveBeenCalledWith('1', 2.49);
});
});
Debugging Next.js
// Enable Next.js debugging
// next.config.js
module.exports = {
// Enable source maps in production
productionBrowserSourceMaps: true,
// Logging
logging: {
fetches: {
fullUrl: true,
},
},
};
// Client-side debugging
'use client';
export function PricingDashboard() {
useEffect(() => {
// React DevTools
console.log('Component mounted');
// Next.js DevTools
if (process.env.NODE_ENV === 'development') {
console.log('Development mode');
}
}, []);
}
CI/CD Pipeline
# .github/workflows/deploy.yml
name: Deploy Pricing Frontend
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm type-check
- name: Lint
run: pnpm lint
- name: Test
run: pnpm test
- name: Build
run: pnpm build
env:
NEXT_PUBLIC_GRAPHQL_ENDPOINT: ${{ secrets.GRAPHQL_ENDPOINT }}
NEXT_PUBLIC_COGNITO_CLIENT_ID: ${{ secrets.COGNITO_CLIENT_ID }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy with SST
run: |
pnpm add -g sst
sst deploy --stage production
Summary
The pricing-frontend is built entirely on Next.js 14, React 18, and TypeScript through:
- Next.js 14 App Router for modern routing and SSR
- React 18 with concurrent features for smooth UI updates
- TypeScript for type safety throughout
- Server-Side Rendering for fast initial page loads
- Jotai for lightweight UI state management
- React Query for server state, caching, and synchronization
- GraphQL integration with real-time subscriptions
- Material-UI for consistent, professional UI components
- NextAuth.js with Cognito for authentication and RBAC
- SST Framework for serverless deployment on AWS
The application demonstrates production-grade Next.js expertise at enterprise scale:
- Real-time franchisee pricing for 8,000+ locations
- Fast loading times through SSR and optimization techniques
- Streamlined pricing operations for Taco Bell staff
- Role-based access control for secure pricing management
- Serverless architecture with automatic scaling
This architecture showcases deep understanding of Next.js runtime characteristics, React 18 concurrent features, serverless deployment patterns, and high-performance TypeScript/JavaScript development for enterprise-scale pricing management systems.