Next.js 15 फुलस्टैक (App Router + Server Actions) · पाठ

Server और Edge में Structured Logging

ऐसे correlated, structured logs दर्ज करें जो serverless cold starts और Edge constraints के बावजूद उपलब्ध रहें।

पाठ 3, कुल 4 में से13 चरण

Server और Edge में Structured Logging, CoddyKit पर Next.js 15 फुलस्टैक (App Router + Server Actions) का एक निःशुल्क पाठ है। यह 4 में से 3वाँ पाठ है। इस अध्ययन पथ के 3 तक कोई भी पाठ पूरा पढ़ना निःशुल्क है — इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ व्यावहारिक अभ्यास भी उपलब्ध कराता है। यह Next.js 15 फुलस्टैक (App Router + Server Actions) सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। Next.js 15 फुलस्टैक (App Router + Server Actions) पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

Next.js 15 में संरचित लॉगिंग क्यों महत्वपूर्ण है

पारंपरिक console.log असंरचित स्ट्रिंग आउटपुट करता है। सर्वरलेस फ़ंक्शन और Edge रनटाइम पर चलने वाले प्रोडक्शन Next.js 15 ऐप में ये स्ट्रिंग लगभग बेकार होती हैं: अनुरोधों के बीच कोई संबंध नहीं होता, मशीन द्वारा पार्स किए जा सकने वाले फ़ील्ड नहीं होते, और कोल्ड स्टार्ट बफ़र किए गए आउटपुट को फ्लश होने से पहले ही हटा देते हैं।

संरचित लॉगिंग प्रत्येक लॉग पंक्ति में समान फ़ील्ड वाले JSON ऑब्जेक्ट उत्पन्न करके इस समस्या का समाधान करती है:

  • requestId — प्रत्येक लॉग को एक HTTP अनुरोध से जोड़ता है
  • समय-मुद्रा — ISO-8601, हमेशा UTC
  • स्तर — info / warn / error
  • सेवा — किस रूट या फ़ंक्शन ने लॉग उत्पन्न किया
  • संदेश — मनुष्य द्वारा पढ़ा जा सकने वाला सारांश
  • संदर्भ — मनमाना संरचित पेलोड

निरीक्षण प्लेटफ़ॉर्म (Datadog, Axiom, Grafana Loki) इन फ़ील्ड को अपने-आप ग्रहण करते हैं, जिससे आप कुछ ही सेकंड में हज़ारों समवर्ती अनुरोधों को फ़िल्टर और संबंधित कर सकते हैं।

मुख्य लॉगर इंटरफ़ेस

किसी भी रनटाइम के साथ एकीकरण करने से पहले न्यूनतम, पोर्टेबल लॉगर इंटरफ़ेस परिभाषित करें। इससे आपका व्यावसायिक तर्क ठोस लॉगिंग कार्यान्वयन से अलग रहता है और परीक्षण बहुत सरल हो जाता है।

lib/logger/types.ts में वह संरचना बनाएँ जिसे प्रत्येक लॉगर को पूरा करना होगा:

// lib/logger/types.ts

export type LogLevel = 'debug' | 'info' | 'warn' | 'error';

export interface LogContext {
  requestId?: string;
  userId?: string;
  route?: string;
  durationMs?: number;
  [key: string]: unknown;
}

export interface Logger {
  debug(message: string, context?: LogContext): void;
  info(message: string, context?: LogContext): void;
  warn(message: string, context?: LogContext): void;
  error(message: string, context?: LogContext & { err?: unknown }): void;
}

export interface LogEntry {
  timestamp: string;
  level: LogLevel;
  service: string;
  message: string;
  context: LogContext;
}

Node.js रनटाइम के लिए JSON लॉगर बनाना

Next.js 15 Server Components, Route Handlers और Server Actions डिफ़ॉल्ट रूप से Node.js रनटाइम में चलते हैं। यहाँ आपको process.stdout की सुविधा मिलती है और आप सुरक्षित रूप से कई पंक्तियों वाला JSON उत्पन्न कर सकते हैं।

मुख्य अनुशासन यह है: प्रति पंक्ति एक JSON ऑब्जेक्ट लिखें (NDJSON / JSON Lines प्रारूप)। लॉग एग्रीगेटर नई पंक्तियों के आधार पर विभाजित करते हैं, इसलिए कई पंक्तियों वाला आउटपुट ग्रहण प्रक्रिया को दूषित कर देता है।

ध्यान दें कि err को मैन्युअल रूप से क्रमबद्ध किया गया है — JSON.stringify चुपचाप Error की stack और message जैसी प्रॉपर्टी हटा देता है।

// lib/logger/node-logger.ts
import type { Logger, LogContext, LogEntry, LogLevel } from './types';

function serializeError(err: unknown): Record<string, unknown> {
  if (err instanceof Error) {
    return { name: err.name, message: err.message, stack: err.stack };
  }
  return { raw: String(err) };
}

function createEntry(
  level: LogLevel,
  service: string,
  message: string,
  context: LogContext = {}
): LogEntry {
  return {
    timestamp: new Date().toISOString(),
    level,
    service,
    message,
    context,
  };
}

export function createNodeLogger(service: string): Logger {
  const write = (entry: LogEntry) =>
    process.stdout.write(JSON.stringify(entry) + '\n');

  return {
    debug: (msg, ctx) => write(createEntry('debug', service, msg, ctx)),
    info:  (msg, ctx) => write(createEntry('info',  service, msg, ctx)),
    warn:  (msg, ctx) => write(createEntry('warn',  service, msg, ctx)),
    error: (msg, ctx) => {
      const { err, ...rest } = ctx ?? {};
      write(createEntry('error', service, msg, {
        ...rest,
        ...(err !== undefined ? { error: serializeError(err) } : {}),
      }));
    },
  };
}

Edge-संगत लॉगर: सीमा में टिके रहना

Edge Runtime (जिसका उपयोग Middleware और export const runtime = 'edge' वाले Route Handlers करते हैं) अधिकांश Node.js API हटा देता है। आप process.stdout.write या fs का उपयोग नहीं कर सकते।

आप इनका उपयोग कर सकते हैं:

  • console.log / console.error — हमेशा उपलब्ध
  • Fetch API — लॉग को किसी बाहरी एंडपॉइंट पर भेजने के लिए
  • crypto.randomUUID() — अनुरोध ID के लिए

युक्ति यह है: console.log के माध्यम से JSON स्ट्रिंग ही उत्पन्न करते रहें। Vercel Edge लॉग पंक्ति-बफ़र किए जाते हैं और आपके लॉग ड्रेन को साधारण टेक्स्ट के रूप में भेजे जाते हैं, इसलिए प्रति पंक्ति एक JSON सुरक्षित है।

// lib/logger/edge-logger.ts
import type { Logger, LogContext, LogEntry, LogLevel } from './types';

function createEntry(
  level: LogLevel,
  service: string,
  message: string,
  context: LogContext = {}
): LogEntry {
  return {
    timestamp: new Date().toISOString(),
    level,
    service,
    message,
    context,
  };
}

export function createEdgeLogger(service: string): Logger {
  const emit = (entry: LogEntry) => {
    // console.log is the only safe output channel in Edge Runtime
    const line = JSON.stringify(entry);
    if (entry.level === 'error') {
      console.error(line);
    } else {
      console.log(line);
    }
  };

  return {
    debug: (msg, ctx) => emit(createEntry('debug', service, msg, ctx)),
    info:  (msg, ctx) => emit(createEntry('info',  service, msg, ctx)),
    warn:  (msg, ctx) => emit(createEntry('warn',  service, msg, ctx)),
    error: (msg, ctx) => emit(createEntry('error', service, msg, ctx)),
  };
}

AsyncLocalStorage के साथ अनुरोध सहसंबंध

सर्वरलेस लॉगिंग की सबसे कठिन समस्या सहसंबंध है: एक अनुरोध के दौरान उत्पन्न हर लॉग में समान requestId जोड़ना, वह भी प्रत्येक स्थान पर ID को पैरामीटर के रूप में पास किए बिना, यहाँ तक कि गहराई में स्थित यूटिलिटी फ़ंक्शन में भी।

Node.js का AsyncLocalStorage इस समस्या का समाधान करता है। यह प्रति-अनुरोध संदर्भ स्टोर बनाता है, जो किसी स्पष्ट थ्रेडिंग के बिना async श्रृंखलाओं — await, Promise.then, टाइमर — में अपने-आप आगे बढ़ता है।

Next.js 15 Node.js रनटाइम में इसका मूल समर्थन करता है। एक स्टोर मॉड्यूल बनाएँ:

// lib/logger/request-store.ts
import { AsyncLocalStorage } from 'async_hooks';

export interface RequestStore {
  requestId: string;
  userId?: string;
  startTime: number;
}

// One singleton for the process lifetime
export const requestStore = new AsyncLocalStorage<RequestStore>();

export function getRequestContext(): Partial<RequestStore> {
  return requestStore.getStore() ?? {};
}

// Wrap any async work in this to bind a store
export function runWithRequestContext<T>(
  store: RequestStore,
  fn: () => Promise<T>
): Promise<T> {
  return requestStore.run(store, fn);
}

स्टोर का उपयोग करने वाला संदर्भ-जागरूक लॉगर

अब AsyncLocalStorage स्टोर को लॉगर से जोड़ें। प्रत्येक लॉग कॉल चल रहे संदर्भ से requestId और userId अपने-आप पढ़ेगी — props को कई स्तरों तक पास करने की आवश्यकता नहीं होगी।

लिखने से पहले परिवेशीय स्टोर को मर्ज करने के लिए lib/logger/node-logger.ts अपडेट करें:

// lib/logger/context-logger.ts
import { createNodeLogger } from './node-logger';
import { getRequestContext } from './request-store';
import type { Logger, LogContext } from './types';

export function createContextLogger(service: string): Logger {
  const base = createNodeLogger(service);

  function enrich(ctx: LogContext = {}): LogContext {
    const { requestId, userId, startTime } = getRequestContext();
    return {
      ...(requestId ? { requestId } : {}),
      ...(userId    ? { userId }    : {}),
      ...(startTime
        ? { elapsedMs: Date.now() - startTime }
        : {}),
      ...ctx, // caller can override ambient values if needed
    };
  }

  return {
    debug: (msg, ctx) => base.debug(msg, enrich(ctx)),
    info:  (msg, ctx) => base.info(msg,  enrich(ctx)),
    warn:  (msg, ctx) => base.warn(msg,  enrich(ctx)),
    error: (msg, ctx) => base.error(msg, enrich(ctx)),
  };
}

// Singleton used everywhere in Node.js routes
export const logger = createContextLogger('nextjs-app');

Middleware में संदर्भ डालना

requestId निर्धारित करने का आदर्श स्थान Next.js Middleware है — यह रूटिंग से पहले प्रत्येक अनुरोध को रोकता है। आप एक ID बना सकते हैं, उसे अनुरोध हेडर (x-request-id) से जोड़ सकते हैं और फिर उसे Server Components तथा API रूट, दोनों को आगे भेज सकते हैं।

ध्यान दें: Middleware Edge Runtime में चलता है। uuid पैकेज के बजाय crypto.randomUUID() और createEdgeLogger का उपयोग करें।

// middleware.ts  (project root)
import { NextRequest, NextResponse } from 'next/server';
import { createEdgeLogger } from '@/lib/logger/edge-logger';

const log = createEdgeLogger('middleware');

export function middleware(req: NextRequest) {
  // Honour an upstream gateway's ID if present
  const requestId =
    req.headers.get('x-request-id') ?? crypto.randomUUID();

  const start = Date.now();

  log.info('request started', {
    requestId,
    method: req.method,
    path: req.nextUrl.pathname,
  });

  const response = NextResponse.next();

  // Forward the ID so Route Handlers and Server Actions can read it
  response.headers.set('x-request-id', requestId);

  log.info('request forwarded', {
    requestId,
    durationMs: Date.now() - start,
  });

  return response;
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Route Handler में AsyncLocalStorage को आरंभ करना

Node.js Route Handlers में x-request-id हेडर (जो Middleware द्वारा सेट किया गया है) पढ़ें और AsyncLocalStorage स्टोर को आरंभ करें। runWithRequestContext के भीतर बुलाया गया सब कुछ — जिसमें नेस्टेड await और यूटिलिटी फ़ंक्शन शामिल हैं — इस संदर्भ को अपने-आप प्राप्त करता है।

// app/api/orders/route.ts
import { type NextRequest, NextResponse } from 'next/server';
import { runWithRequestContext } from '@/lib/logger/request-store';
import { logger } from '@/lib/logger/context-logger';
import { fetchOrders } from '@/lib/orders';

export async function GET(req: NextRequest) {
  const requestId =
    req.headers.get('x-request-id') ?? crypto.randomUUID();

  return runWithRequestContext(
    { requestId, startTime: Date.now() },
    async () => {
      logger.info('fetching orders');
      // up arrow automatically includes requestId + elapsedMs

      try {
        const orders = await fetchOrders();
        logger.info('orders fetched', { count: orders.length });
        return NextResponse.json(orders);
      } catch (err) {
        logger.error('failed to fetch orders', { err });
        return NextResponse.json(
          { error: 'Internal Server Error' },
          { status: 500 }
        );
      }
    }
  );
}

Server Actions के भीतर लॉगिंग

Next.js 15 में Server Actions सर्वर पर Node.js रनटाइम में चलते हैं, इसलिए यहाँ भी AsyncLocalStorage काम करता है। हालाँकि, एक सूक्ष्म सीमा है: क्रिया को कच्चे HTTP हैंडलर के बजाय React रेंडरर चलाता है, इसलिए आपको क्रिया के आरंभ में स्वयं स्टोर को आरंभ करना होगा।

एक सामान्य पैटर्न ऐसा रैपर यूटिलिटी है जो स्टोर को आरंभ करती है और लॉगर उपलब्ध कराती है, जिससे प्रत्येक क्रिया का बॉडी साफ़ रहता है:

// lib/actions/with-logging.ts
import { headers } from 'next/headers';
import { runWithRequestContext } from '@/lib/logger/request-store';
import { logger } from '@/lib/logger/context-logger';

type ActionFn<TArgs extends unknown[], TResult> =
  (...args: TArgs) => Promise<TResult>;

export function withLogging<TArgs extends unknown[], TResult>(
  name: string,
  fn: ActionFn<TArgs, TResult>
): ActionFn<TArgs, TResult> {
  return async (...args) => {
    const headersList = await headers();
    const requestId =
      headersList.get('x-request-id') ?? crypto.randomUUID();

    return runWithRequestContext(
      { requestId, startTime: Date.now() },
      async () => {
        logger.info('action started: ' + name, { action: name });
        try {
          const result = await fn(...args);
          logger.info('action completed: ' + name);
          return result;
        } catch (err) {
          logger.error('action failed: ' + name, { err });
          throw err;
        }
      }
    );
  };
}

// Usage in a Server Action:
// export const submitOrder = withLogging('submitOrder', async (data) => { ... });

प्रोडक्शन में नमूनाकरण और लॉग स्तर

अधिक ट्रैफ़िक वाले Next.js ऐप में प्रत्येक debug कथन को लॉग करना महँगा होता है — कंप्यूट समय और ग्रहण लागत, दोनों के कारण। इन दो तकनीकों का साथ में उपयोग करें:

  • स्तर नियंत्रण: process.env.LOG_LEVEL जाँचें और सीमा से नीचे के स्तर छोड़ दें। प्रोडक्शन में आम तौर पर info चलता है; debug केवल घटनाओं के दौरान सक्षम किया जाता है।
  • नमूनाकरण: अधिक शोर वाले info पथों (जैसे स्वास्थ्य जाँच) के लिए संकेत पूरी तरह खोए बिना मात्रा घटाने हेतु केवल कुछ प्रतिशत अनुरोधों को लॉग करें।

मुख्य नमूनाकरण और स्तर-नियंत्रण तर्क का एक स्वतंत्र प्रदर्शन:

// Standalone demo — runs without any framework
const LEVELS = ['debug', 'info', 'warn', 'error'];

const MIN_LEVEL = process.env['LOG_LEVEL'] || 'info';

function shouldLog(level) {
  return LEVELS.indexOf(level) >= LEVELS.indexOf(MIN_LEVEL);
}

function sample(rate) {
  // rate = 0.1 means log 10% of the time
  return Math.random() < rate;
}

function log(level, message, sampleRate = 1) {
  if (!shouldLog(level)) return;
  if (!sample(sampleRate)) return;
  console.log(JSON.stringify({ level, message, ts: new Date().toISOString() }));
}

// Simulated high-frequency health-check path — logs only ~10%
for (let i = 0; i < 20; i++) {
  log('info', '/api/health called', 0.1);
}

// Errors always log regardless of sample rate
log('error', 'Database connection failed');

लॉग को बाहरी गंतव्य पर भेजना

सर्वरलेस फ़ंक्शन अस्थायी होते हैं — stdout तभी विश्वसनीय है जब आपका प्लेटफ़ॉर्म उसे ग्रहण करे (Vercel ऐसा करता है; साधारण AWS Lambda डिफ़ॉल्ट रूप से नहीं करता)। एक लॉग ड्रेन / दूरस्थ गंतव्य (Axiom, Better Stack, Datadog) स्थायित्व सुनिश्चित करता है।

Edge Runtime से आप waitUntil के साथ fetch का उपयोग करके लॉग भेज सकते हैं, ताकि लॉग POST पृष्ठभूमि में होने के दौरान HTTP प्रतिक्रिया तुरंत लौट आए। Node.js Route Handlers से इसी गैर-अवरोधक गारंटी के लिए after() (Next.js 15) का उपयोग करें।

// lib/logger/axiom-drain.ts
// Edge-compatible log drain using fetch

const AXIOM_DATASET = process.env['AXIOM_DATASET'] ?? '';
const AXIOM_TOKEN   = process.env['AXIOM_API_TOKEN'] ?? '';

export async function sendToAxiom(
  entries: object[]
): Promise<void> {
  if (!AXIOM_DATASET || !AXIOM_TOKEN) return; // skip in dev

  await fetch(
    'https://api.axiom.co/v1/datasets/' + AXIOM_DATASET + '/ingest',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-ndjson',
        Authorization: 'Bearer ' + AXIOM_TOKEN,
      },
      // NDJSON: one JSON object per line
      body: entries.map((e) => JSON.stringify(e)).join('\n'),
    }
  );
}

// In a Middleware or Edge Route Handler:
// context.waitUntil(sendToAxiom([entry]));
//
// In a Node.js Route Handler (Next.js 15):
// import { after } from 'next/server';
// after(() => sendToAxiom([entry]));

त्वरित जाँच: रनटाइम के बीच सहसंबंध

आपको एकल उपयोगकर्ता अनुरोध की प्रत्येक लॉग पंक्ति — Middleware (Edge), Route Handler (Node.js) और नेस्टेड Server Action में फैली हुई — में समान requestId साझा करनी है। कौन-सा तरीका सही है?

पाठ का पुनरावलोकन: सर्वर और Edge में संरचित लॉगिंग

इस पाठ में आपने Next.js 15 के लिए प्रोडक्शन-स्तर की एक पूर्ण संरचित लॉगिंग पाइपलाइन बनाई:

  • पोर्टेबल इंटरफ़ेस (Logger, LogContext) व्यावसायिक तर्क को लॉगिंग बैकएंड से अलग रखता है।
  • रनटाइम-विशिष्ट कार्यान्वयन: createNodeLogger, process.stdout में NDJSON लिखता है; createEdgeLogger, console.log का उपयोग करता है — Edge Runtime में सुरक्षित एकमात्र आउटपुट चैनल।
  • AsyncLocalStorage के माध्यम से सहसंबंध: किसी स्टोर को एक बार आरंभ करें (Route Handler या Server Action रैपर में), और प्रत्येक नेस्टेड await को requestId, userId और बीता हुआ समय अपने-आप प्राप्त हो जाएगा।
  • Middleware अनुरोध-ID का स्रोत है: Middleware में किसी upstream x-request-id हेडर को बनाएँ या स्वीकार करें, उसे आगे भेजें और Node.js स्टोर को पहुँचते ही आरंभ करें।
  • प्रोडक्शन अनुशासन: LOG_LEVEL env चर से लॉग स्तर नियंत्रित करें, शोर वाले पथों का नमूनाकरण करें, Error ऑब्जेक्ट को स्पष्ट रूप से क्रमबद्ध करें और प्रतिक्रियाओं को अवरुद्ध होने से बचाने के लिए waitUntil या after() के साथ लॉग को स्थायी बाहरी गंतव्य पर भेजें।

ये पैटर्न मिलकर ऐसा संबंधित, मशीन द्वारा पार्स किया जा सकने वाला निरीक्षण तंत्र प्रदान करते हैं जो कोल्ड स्टार्ट, समवर्ती अनुरोधों और Node.js तथा Edge रनटाइम की सीमाओं के बावजूद काम करता रहता है।

शुरुआत निःशुल्क

एआई शिक्षक के साथ TypeScript सीखें — निःशुल्क

अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।

पाठ्यक्रम
22
पाठ
88

अक्सर पूछे जाने वाले प्रश्न

क्या “Server और Edge में Structured Logging” पाठ निःशुल्क है?

हाँ — Next.js 15 फुलस्टैक (App Router + Server Actions) अध्ययन पथ के 3 तक कोई भी पाठ, जिसमें “Server और Edge में Structured Logging” भी शामिल है, यहाँ वेब पर पूरा पढ़ना निःशुल्क है। इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ इंटरैक्टिव अभ्यास भी उपलब्ध कराता है। Next.js 15 फुलस्टैक (App Router + Server Actions) पाठ्यक्रम में कुल 4 पाठ शामिल हैं।

“Server और Edge में Structured Logging” में मैं क्या सीखूँगा?

ऐसे correlated, structured logs दर्ज करें जो serverless cold starts और Edge constraints के बावजूद उपलब्ध रहें। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ Next.js 15 फुलस्टैक (App Router + Server Actions) का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।

क्या Next.js 15 फुलस्टैक (App Router + Server Actions) शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?

पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर Next.js 15 फुलस्टैक (App Router + Server Actions) शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 3वाँ पाठ है।

“Server और Edge में Structured Logging” पाठ पूरा करने में कितना समय लगता है?

CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।

क्या मैं इस Next.js 15 फुलस्टैक (App Router + Server Actions) पाठ में कोड लिख और चला सकता हूँ?

हाँ। हर Next.js 15 फुलस्टैक (App Router + Server Actions) पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।

इस पाठ्यक्रम के सभी पाठ

  1. instrumentation.ts से OpenTelemetry Tracing
  2. Granular error.tsx और global-error Boundaries
  3. Server और Edge में Structured Logging
  4. Server Action Failures और Telemetry कैप्चर करना
← Next.js 15 फुलस्टैक (App Router + Server Actions) पर वापस जाएँ