Next.js 16 App Router: Production Patterns and Pitfalls

Production-ready Next.js 16 App Router patterns: server components, caching strategies, metadata API, error boundaries, and performance pitfalls to avoid.

E
ECOSIRE Research and Development Team
|19 मार्च 202612 मिनट पढ़ें2.7k शब्द|

Next.js 16 ऐप राउटर: उत्पादन पैटर्न और नुकसान

नेक्स्ट.जेएस ऐप राउटर ने मौलिक रूप से बदल दिया कि हम रिएक्ट एप्लिकेशन कैसे बनाते हैं, और नेक्स्ट.जेएस 16 तक पैटर्न वास्तव में उत्पादन के लिए तैयार हो गए हैं। लेकिन पेज राउटर से वैचारिक बदलाव तीव्र है - सर्वर घटक, आंशिक प्रीरेंडरिंग, नेस्टेड लेआउट और पूरी तरह से पुन: डिज़ाइन किए गए डेटा फ़ेचिंग मॉडल सभी को मानसिक मॉडल रीवायरिंग की आवश्यकता होती है जिसे दस्तावेज़ीकरण अकेले पूरी तरह से व्यक्त नहीं कर सकता है।

यह मार्गदर्शिका उन पैटर्नों को शामिल करती है जो वास्तव में उत्पादन में मायने रखते हैं: वे जो कैश विषाक्तता को रोकते हैं, अनावश्यक क्लाइंट बंडलों को खत्म करते हैं, और आपके कोर वेब वाइटल्स को बड़े पैमाने पर हरा-भरा रखते हैं। हम 11-लोकेल i18n, डायनेमिक ओजी छवियों और 5,577 एमडीएक्स सामग्री फ़ाइलों के लिए सर्वर-साइड रेंडरिंग के साथ 249-पेज नेक्स्ट.जेएस 16 एप्लिकेशन बनाने के अनुभव से सीखेंगे।

मुख्य बातें

  • उन पेजों पर कभी भी export const metadata का उपयोग न करें जिन्हें स्थानीय-जागरूक SEO की आवश्यकता है - हमेशा generateMetadata() का उपयोग करें
  • सर्वर घटक डिफ़ॉल्ट हैं; केवल 'use client' जोड़ें जब आपको वास्तव में अन्तरक्रियाशीलता की आवश्यकता हो
  • loading.tsx और error.tsx अपने रूट सेगमेंट के साथ मिलते हैं, रूट पर नहीं
  • proxy.ts फ़ाइल Next.js 16 का मिडलवेयर है - middleware.ts और proxy.ts दोनों न बनाएं
  • सर्वर कंपोनेंट्स में fetch() स्वचालित रूप से समान रेंडर के भीतर समान अनुरोधों को डीडुप्लिकेट करता है
  • टैग के साथ unstable_cache हर चीज़ को पुनः मान्य किए बिना सर्जिकल कैश अमान्यकरण को सक्षम बनाता है
  • generateStaticParams() उन गतिशील मार्गों के लिए आवश्यक है जिन्हें आप बिल्ड समय पर स्थिर रूप से उत्पन्न करना चाहते हैं
  • JSON-LD संरचित डेटा को यूनिकोड एन्कोडिंग का उपयोग करके स्क्रिप्ट ब्रेकआउट को स्वच्छ करना होगा

सर्वर घटक मानसिक मॉडल

ऐप राउटर के साथ डेवलपर्स की सबसे बड़ी गलती 'use client' तक बहुत जल्दी पहुंचना है। Next.js 16 में, app/ निर्देशिका में प्रत्येक घटक डिफ़ॉल्ट रूप से एक सर्वर घटक है। यह सही डिफ़ॉल्ट है - सर्वर घटक सर्वर पर प्रस्तुत होते हैं, शून्य जावास्क्रिप्ट बंडल प्रभाव होता है, और सीधे डेटाबेस और एपीआई तक पहुंच सकते हैं।

नियम: अपने घटक वृक्ष की पत्तियों पर 'use client' दबाएँ। एक पृष्ठ एक सर्वर घटक हो सकता है जो डेटा प्राप्त करता है और इसे एक पतले क्लाइंट घटक शेल में भेजता है जो इंटरैक्शन को संभालता है।

// app/[locale]/blog/[slug]/page.tsx — Server Component (no directive needed)
import { getBlogPost } from '@/lib/blog';
import { BlogContent } from '@/components/blog/blog-content'; // Client Component

export default async function BlogPostPage({
  params,
}: {
  params: Promise<{ slug: string; locale: string }>;
}) {
  const { slug, locale } = await params;
  const post = await getBlogPost(slug, locale);

  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      {/* Server Component — no JS sent to browser */}
      <PostMeta author={post.author} date={post.date} />
      {/* Client Component — only this file sent as JS */}
      <BlogContent content={post.content} />
    </article>
  );
}
// components/blog/blog-content.tsx — Client Component
'use client';

import { useState } from 'react';

export function BlogContent({ content }: { content: string }) {
  const [expanded, setExpanded] = useState(false);

  return (
    <div>
      <div>{content}</div>
      {/* Client-side interactivity lives here */}
    </div>
  );
}

मुख्य जानकारी: BlogContent जावास्क्रिप्ट को ब्राउज़र पर भेजता है। PostMeta नहीं है. प्रत्येक अनावश्यक 'use client' निर्देश आपके बंडल को बढ़ा देता है।


स्टेटिक मेटाडेटा पर जेनरेटमेटाडेटा

स्टेटिक export const metadata अपनी सादगी के लिए आकर्षक है, लेकिन यह स्थानीय-जागरूक नहीं हो सकता है, रूट पैरामीटर तक नहीं पहुंच सकता है, और डेटा प्राप्त नहीं कर सकता है। किसी भी वास्तविक एप्लिकेशन के लिए, generateMetadata() एकमात्र सही विकल्प है:

// app/[locale]/blog/[slug]/page.tsx
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string; locale: string }>;
}): Promise<Metadata> {
  const { slug, locale } = await params;
  const post = await getBlogPost(slug, locale);

  if (!post) return {};

  const locales = ['en', 'zh', 'es', 'ar', 'pt', 'fr', 'de', 'ja', 'tr', 'hi', 'ur'];

  return {
    title: post.title,
    description: post.description,
    alternates: {
      canonical: `https://ecosire.com/${locale === 'en' ? '' : locale + '/'}blog/${slug}`,
      languages: Object.fromEntries(
        locales.map((loc) => [
          loc,
          `https://ecosire.com/${loc === 'en' ? '' : loc + '/'}blog/${slug}`,
        ])
      ),
    },
    openGraph: {
      title: post.title,
      description: post.description,
      images: [`/api/og/blog/${slug}?locale=${locale}`],
      type: 'article',
      publishedTime: post.date,
    },
  };
}

alternates.languages फ़ील्ड प्रत्येक स्थान के लिए hreflang टैग उत्पन्न करता है - जो बहुभाषी SEO के लिए आवश्यक है। खोज इंजन प्रत्येक उपयोगकर्ता को सही भाषा संस्करण प्रदान करने के लिए इनका उपयोग करते हैं।


नेस्टेड लेआउट और साझा राज्य

ऐप राउटर का नेस्टेड लेआउट सिस्टम शक्तिशाली है लेकिन इसमें एक गैर-स्पष्ट बाधा है: लेआउट प्रॉप्स के माध्यम से अपने बच्चों को डेटा पास नहीं कर सकते हैं। इसका समाधान या तो रिएक्ट कॉन्टेक्स्ट (केवल क्लाइंट घटक) है या प्रत्येक स्तर पर स्वतंत्र रूप से डेटा प्राप्त करना है।

app/
  [locale]/
    layout.tsx          <- Locale layout: fonts, i18n provider, theme
    page.tsx            <- Homepage
    blog/
      layout.tsx        <- Blog layout: sidebar, breadcrumbs
      page.tsx          <- Blog list
      [slug]/
        layout.tsx      <- Post layout: reading progress, TOC
        page.tsx        <- Post content
        loading.tsx     <- Skeleton while post loads
        error.tsx       <- Error boundary for this segment

प्रत्येक layout.tsx अपने बच्चों को स्वतंत्र रूप से लपेटता है। पैरेंट लेआउट में लाया गया डेटा स्वचालित रूप से बच्चों के लिए उपलब्ध नहीं होता है - प्रत्येक घटक जिसे डेटा की आवश्यकता होती है वह इसे स्वतंत्र रूप से प्राप्त करता है। Next.js 16 एक रेंडर चक्र के भीतर समान fetch() कॉल को डीडुप्लिकेट करता है, इसलिए यह उतना महंगा नहीं है जितना लगता है।

// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}) {
  const { locale } = await params;
  const messages = await getMessages();

  return (
    <html lang={locale} dir={['ar', 'ur'].includes(locale) ? 'rtl' : 'ltr'}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

कैशिंग रणनीति: चार परतें

Next.js 16 में चार अलग-अलग कैशिंग परतें हैं। कोई पेज अपडेट क्यों हो रहा है या नहीं हो रहा है, इसके बारे में तर्क करने के लिए इन चारों को समझना आवश्यक है:

1. अनुरोध ज्ञापन - एक रेंडर चक्र के भीतर समान fetch() URL को एक बार कॉल किया जाता है। स्वचालित, कोई कॉन्फ़िगरेशन नहीं.

2. डेटा कैश - सभी अनुरोधों पर लगातार। fetch() प्रतिक्रियाएँ डिफ़ॉल्ट रूप से अनिश्चित काल तक कैश की जाती हैं।

// Force revalidation every hour
const data = await fetch('/api/posts', { next: { revalidate: 3600 } });

// Never cache (equivalent to SSR)
const data = await fetch('/api/posts', { cache: 'no-store' });

3. पूर्ण रूट कैश - स्थिर मार्गों के लिए निर्माण समय पर स्थिर HTML और RSC पेलोड कैश किया गया।

4. राउटर कैश - विज़िट किए गए मार्गों का क्लाइंट-साइड कैश। ब्राउज़र सत्र के लिए बनी रहती है.

डेटाबेस क्वेरीज़ (गैर-फ़ेच) के लिए, unstable_cache का उपयोग करें:

import { unstable_cache } from 'next/cache';

const getCachedBlogPosts = unstable_cache(
  async (locale: string) => {
    return db.select().from(posts).where(eq(posts.locale, locale));
  },
  ['blog-posts'], // Cache key
  {
    revalidate: 3600, // Revalidate hourly
    tags: ['blog'], // Tag for manual invalidation
  }
);

// Invalidate from a Server Action or Route Handler:
import { revalidateTag } from 'next/cache';
revalidateTag('blog'); // All blog-tagged caches cleared

त्रुटि सीमाएँ: दो स्तरों की आवश्यकता है

उत्पादन अनुप्रयोगों को दो त्रुटि सीमाओं की आवश्यकता होती है: एक स्थानीय-जागरूक पृष्ठों के लिए और एक रूट-स्तरीय त्रुटियों के लिए वैश्विक फ़ॉलबैक।

// app/[locale]/error.tsx — Locale-level error boundary
'use client';

import { useTranslations } from 'next-intl';
import { useEffect } from 'react';

export default function LocaleError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  const t = useTranslations('errors');

  useEffect(() => {
    // Log to error tracking service
    console.error(error);
  }, [error]);

  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <h2>{t('title')}</h2>
      <p>{t('description')}</p>
      <button onClick={reset}>{t('retry')}</button>
    </div>
  );
}
// app/global-error.tsx — Root fallback (no i18n available here)
'use client';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  // Cannot use useTranslations here — no NextIntlClientProvider above
  // Use inline styles — no Tailwind in root error boundaries
  return (
    <html>
      <body>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '2rem' }}>
          <h2 style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>Something went wrong</h2>
          <button
            onClick={reset}
            style={{ marginTop: '1rem', padding: '0.5rem 1rem', cursor: 'pointer' }}
          >
            Try again
          </button>
        </div>
      </body>
    </html>
  );
}

global-error.tsx सक्रिय होने पर रूट layout.tsx को प्रतिस्थापित कर देता है, इसलिए इसमें <html> और <body> टैग शामिल होने चाहिए। यह लेआउट में रहने वाले संदर्भ प्रदाताओं तक नहीं पहुंच सकता - कोई i18n, कोई थीम नहीं, कोई प्रामाणिक संदर्भ नहीं।


मिडलवेयर: प्रॉक्सी.टीएस बनाम मिडलवेयर.टीएस

Next.js एज मिडलवेयर के लिए middleware.ts (या middleware.js) का उपयोग करता है। नेक्स्ट-इंटल v4 का उपयोग करने वाले प्रोजेक्ट में, मिडलवेयर सेटअप का नाम अक्सर proxy.ts कर दिया जाता है और पुनः निर्यात किया जाता है, जिससे एक गंभीर ख़तरा पैदा होता है: middleware.ts और proxy.ts दोनों न बनाएं। दोनों के होने से निर्माण त्रुटि होती है।

i18n को प्रमाणीकरण सुरक्षा के साथ संयोजित करने का सही पैटर्न:

// src/proxy.ts — This IS the Next.js middleware
import createMiddleware from 'next-intl/middleware';
import { NextRequest, NextResponse } from 'next/server';
import { routing } from './i18n/routing';

const intlMiddleware = createMiddleware(routing);

const protectedPaths = ['/dashboard', '/portal'];

export default function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // Check auth for protected paths
  const isProtected = protectedPaths.some((path) =>
    pathname.includes(path)
  );

  if (isProtected) {
    const token = request.cookies.get('ecosire_auth');
    if (!token) {
      const url = request.nextUrl.clone();
      url.pathname = '/auth/login';
      // Prevent open redirect — never allow // prefix
      const redirect = pathname.startsWith('//') ? '/' : pathname;
      url.searchParams.set('redirect', redirect);
      return NextResponse.redirect(url);
    }
  }

  return intlMiddleware(request);
}

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

यह एकल फ़ाइल i18n लोकेल डिटेक्शन और ऑथ सुरक्षा दोनों को संभालती है। config.matcher में स्थिर फ़ाइलें, API रूट और Next.js आंतरिक शामिल नहीं हैं।


JSON-LD संरचित डेटा सुरक्षा

JSON-LD स्क्रिप्ट एक सामान्य XSS वेक्टर हैं। यदि आपके डेटा में क्लोजिंग स्क्रिप्ट टैग है, तो यह स्क्रिप्ट ब्लॉक से बाहर हो सकता है। समाधान अनिवार्य है - कम-से-कम वर्ण को एन्कोड करें:

// components/seo/JsonLd.tsx
interface JsonLdProps {
  data: Record<string, unknown>;
}

export function JsonLd({ data }: JsonLdProps) {
  // Sanitize script tag breakout attempts by encoding '<' as unicode
  const json = JSON.stringify(data).replace(/</g, '\\u003c');

  return (
    <script
      type="application/ld+json"
      // Safe: content is JSON-serialized and sanitized above
      suppressHydrationWarning
      ref={(el) => { if (el) el.textContent = json; }}
    />
  );
}

एक पेज में उपयोग:

<JsonLd
  data={{
    '@context': 'https://schema.org',
    '@type': 'BlogPosting',
    headline: post.title,
    description: post.description,
    inLanguage: locale,
    author: {
      '@type': 'Organization',
      name: 'ECOSIRE',
    },
  }}
/>

inLanguage फ़ील्ड बहुभाषी SEO के लिए आवश्यक है - यह AI क्रॉलर्स और खोज इंजनों को बताता है कि सामग्री किस भाषा में है, जिससे गैर-अंग्रेजी प्रश्नों के लिए रैंकिंग सटीकता में सुधार होता है।


गतिशील ओजी छवियाँ

Next.js 16 का ImageResponse API किनारे पर प्रति पृष्ठ OG छवियां उत्पन्न करता है। ब्लॉग पोस्ट के लिए सेटअप:

// app/api/og/blog/[slug]/route.tsx
import { ImageResponse } from 'next/og';

export const runtime = 'edge';

export async function GET(
  request: Request,
  { params }: { params: Promise<{ slug: string }> }
) {
  const { slug } = await params;
  const post = await getPostMeta(slug);

  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
          justifyContent: 'flex-end',
          width: '100%',
          height: '100%',
          background: 'linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%)',
          padding: '60px',
        }}
      >
        <div style={{ fontSize: 48, fontWeight: 700, color: 'white', lineHeight: 1.2 }}>
          {post.title}
        </div>
        <div style={{ fontSize: 24, color: '#94a3b8', marginTop: 20 }}>
          {post.description}
        </div>
      </div>
    ),
    { width: 1200, height: 630 }
  );
}

सामान्य नुकसान और समाधान

नुकसान 1: जब सर्वर घटक काम करेंगे तो क्लाइंट घटकों में डेटा प्राप्त करना

यदि कोई घटक केवल डेटा प्रदर्शित करता है और उसे रिएक्ट स्थिति या ब्राउज़र एपीआई की आवश्यकता नहीं है, तो यह एक सर्वर घटक होना चाहिए। 'use client' जोड़ने से आयातित सभी चीजें क्लाइंट बंडल में आ जाती हैं।

नुकसान 2: एसिंक सर्वर घटकों के आसपास गुम सस्पेंस सीमाएँ

// Without Suspense — entire page blocks until data loads
export default async function Page() {
  const data = await slowFetch(); // 2 seconds
  return <div>{data}</div>;
}

// With Suspense — page streams, slow part shows skeleton
export default function Page() {
  return (
    <Suspense fallback={<DataSkeleton />}>
      <SlowDataComponent />
    </Suspense>
  );
}

नुकसान 3: कैश्ड फ़ंक्शंस में कुकीज़() या हेडर() का उपयोग करना

cookies() और headers() डायनेमिक एपीआई हैं जो डायनेमिक रेंडरिंग में रूट चुनते हैं। unstable_cache कॉलबैक के अंदर उनका उपयोग करने से कैश चुपचाप टूट जाता है।

नुकसान 4: नेक्स्ट.जेएस 16 में पैरामीटर्स का इंतजार करना भूल जाना

Next.js 16 में, params अब एक वादा है। await को भूलने से रनटाइम त्रुटियाँ होती हैं:

// Next.js 15 — params was synchronous
export default function Page({ params }: { params: { slug: string } }) {
  const { slug } = params;
}

// Next.js 16 — params is async
export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params; // Required
}

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

मुझे जेनरेटस्टैटिकपैराम्स() बनाम डायनेमिक रेंडरिंग का उपयोग कब करना चाहिए?

ऐसी सामग्री के लिए generateStaticParams() का उपयोग करें जो बार-बार बदलती है (ब्लॉग पोस्ट, उत्पाद पृष्ठ, दस्तावेज़ीकरण)। यह शून्य रनटाइम लागत के साथ निर्माण समय पर स्थिर HTML उत्पन्न करता है। वैयक्तिकृत सामग्री, वास्तविक समय डेटा, या हजारों वेरिएंट वाले पृष्ठों के लिए गतिशील प्रतिपादन का उपयोग करें जहां निर्माण समय निषेधात्मक होगा। कभी-कभी बदलने वाली सामग्री के लिए दोनों को ISR (revalidate) के साथ मिलाएं।

मैं ऐप राउटर में प्रमाणीकरण कैसे संभालूं?

ऑथ टोकन को केवल Httpकुकीज़ में संग्रहीत करें - कभी भी लोकलस्टोरेज या सेशनस्टोरेज नहीं। संरक्षित मार्गों के लिए कुकी की उपस्थिति की जांच करने के लिए मिडलवेयर (प्रॉक्सी.टीएस) का उपयोग करें। सर्वर घटकों में, next/headers से cookies() वाली कुकीज़ पढ़ें। उपयोगकर्ता डेटा को सर्वर से क्लाइंट तक प्रॉप्स या रिएक्ट कॉन्टेक्स्ट के माध्यम से पास करें, क्लाइंट कंपोनेंट्स में कुकी को दोबारा पढ़कर कभी नहीं।

loading.tsx और सस्पेंस के बीच क्या अंतर है?

loading.tsx एक विशेष फ़ाइल है जिसे Next.js स्वचालित रूप से संपूर्ण मार्ग खंड के लिए एक सस्पेंस सीमा में लपेट देता है। सेगमेंट की एसिंक सामग्री लोड होने पर यह तुरंत दिखाई देता है। मैन्युअल Suspense सीमाएँ आपको बेहतर नियंत्रण प्रदान करती हैं - आप किसी पृष्ठ के विभिन्न हिस्सों के लिए अलग-अलग कंकाल दिखा सकते हैं। दोनों समान अंतर्निहित रिएक्ट सस्पेंस तंत्र का उपयोग करते हैं।

मैं सर्वर और क्लाइंट घटकों के बीच स्थिति कैसे साझा करूं?

आप ऐसा नहीं कर सकते - सर्वर घटक एक बार प्रस्तुत होते हैं और उनकी कोई रनटाइम स्थिति नहीं होती है। प्रॉप्स के माध्यम से सर्वर से क्लाइंट तक डेटा पास करें। क्लाइंट-साइड स्थिति के लिए जिसे नेविगेशन में जारी रखने की आवश्यकता है, यूआरएल खोज पैरामीटर (साझा करने योग्य स्थिति के लिए), ज़स्टैंड या रेडक्स (क्षणिक स्थिति के लिए), या कुकीज़ (उपयोगकर्ता प्राथमिकताओं के लिए) का उपयोग करें। कभी भी क्लाइंट घटक की स्थिति को सर्वर घटक में आयात करने का प्रयास न करें।

मैं विकास के लिए टर्बोपैक कैसे स्थापित करूं?

अपनी डेव स्क्रिप्ट में --turbo जोड़ें: "dev": "next dev --turbo"। टर्बोपैक नेक्स्ट.जेएस 16 का रस्ट-आधारित बंडलर है जिसमें काफी तेज कोल्ड स्टार्ट और एचएमआर है। यह अधिकांश परियोजनाओं के लिए उत्पादन के लिए तैयार है लेकिन इसमें कुछ वेबपैक प्लगइन असंगतताएं हैं। स्विच करने से पहले टर्बोपैक संगतता सूची के विरुद्ध अपनी विशिष्ट निर्भरता की जाँच करें।


अगले चरण

एक प्रोडक्शन नेक्स्ट.जेएस 16 एप्लिकेशन का निर्माण करना जो 11 स्थानों, हजारों पृष्ठों और एंटरप्राइज़-ग्रेड प्रदर्शन आवश्यकताओं को संभालता है, एक महत्वपूर्ण इंजीनियरिंग उपक्रम है। ECOSIRE की फ्रंटएंड टीम ने बिल्कुल यही भेजा है - बहुभाषी एसईओ, गतिशील ओजी छवियों और उप-सेकंड प्रतिक्रिया समय के साथ 249 पेज का नेक्स्ट.जेएस 16 प्लेटफॉर्म।

यदि आप अपने नेक्स्ट.जेएस एप्लिकेशन का विस्तार कर रहे हैं या आपको एंटरप्राइज़ फ्रंटएंड इंजीनियरिंग सहायता की आवश्यकता है, तो हमारी विकास सेवाओं का पता लगाएं यह देखने के लिए कि हम कैसे मदद कर सकते हैं।

शेयर करें:
E

लेखक

ECOSIRE Research and Development Team

ECOSIRE में एंटरप्राइज़-ग्रेड डिजिटल उत्पाद बना रहे हैं। Odoo एकीकरण, ई-कॉमर्स ऑटोमेशन, और AI-संचालित व्यावसायिक समाधानों पर अंतर्दृष्टि साझा कर रहे हैं।

WhatsApp पर चैट करें