From a03ea880e1b9f1c2f176e0379fcae88a57fb00b3 Mon Sep 17 00:00:00 2001
From: "Mahatthana (Kelvin) Nomsawadi" <me@bboykelvin.dev>
Date: Fri, 11 Oct 2024 21:02:08 +0700
Subject: [PATCH] docs(sdk): Add a guide on how to use the SDK with Next.js
 (#48569)

* Add Next.js to SDK readme

* Fix typo
---
 .../frontend/src/embedding-sdk/README.md      | 163 ++++++++++++++++++
 1 file changed, 163 insertions(+)

diff --git a/enterprise/frontend/src/embedding-sdk/README.md b/enterprise/frontend/src/embedding-sdk/README.md
index 75dbb145b65..493419da11c 100644
--- a/enterprise/frontend/src/embedding-sdk/README.md
+++ b/enterprise/frontend/src/embedding-sdk/README.md
@@ -1189,6 +1189,169 @@ async function fetchRequestToken(url) {
 const config = {fetchRequestToken};
 ```
 
+### Using with Next.js
+
+#### Using App Router
+
+Create a component that imports the `MetabaseProvider` and mark it a React Client component with "use client";
+
+```typescript jsx
+"use client";
+
+import { MetabaseProvider, StaticQuestion } from "@metabase/embedding-sdk-react";
+
+const config = {...}; // Your Metabase SDK configuration
+
+export default function MetabaseComponents() {
+  return (
+    <MetabaseProvider config={config}>
+      <StaticQuestion questionId={QUESTION_ID} />
+    </MetabaseProvider>
+  );
+```
+
+Make sure to use default export, as named export is not supported with this setup.
+
+Then, import this component in your page:
+
+```typescript jsx
+// page.tsx
+
+const MetabaseComponentsNoSsr = dynamic(() => import("@/components/MetabaseComponents"), {
+  ssr: false
+});
+
+export default function HomePage() {
+  return (
+    <>
+      <MetabaseComponentsNoSsr />
+    </>
+  );
+}
+```
+
+> [!CAUTION]
+> If you export the component as a named export, it will not work with Next.js. You must use a default export.
+
+This won't work:
+
+```typescript jsx
+const DynamicAnalytics = dynamic(
+  () => import("@/components/MetabaseComponents").then((module) => module.MetabaseComponents),
+  {
+    ssr: false,
+  }
+);
+```
+
+If you authenticate with Metabase using JWT, you can create a Route handler that signs a user into Metabase.
+
+Create a new `route.ts` file in your `app/*` directory, for example `app/sso/metabase/route.ts` that corresponds to an endpoint at /sso/metabase.
+
+```typescript
+import jwt from "jsonwebtoken";
+
+const METABASE_JWT_SHARED_SECRET = process.env.METABASE_JWT_SHARED_SECRET || "";
+const METABASE_INSTANCE_URL = process.env.METABASE_INSTANCE_URL || "";
+
+export async function GET() {
+  const token = jwt.sign(
+    {
+      email: user.email,
+      first_name: user.firstName,
+      last_name: user.lastName,
+      groups: [user.group],
+      exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minutes expiration
+    },
+    // This is the JWT signing secret in your Metabase JWT authentication setting
+    METABASE_JWT_SHARED_SECRET
+  );
+  const ssoUrl = `${METABASE_INSTANCE_URL}/auth/sso?token=true&jwt=${token}`;
+
+  try {
+    const ssoResponse = await fetch(ssoUrl, { method: "GET" });
+    const ssoResponseBody = await ssoResponse.json();
+
+    return Response.json(ssoResponseBody);
+  } catch (error) {
+    if (error instanceof Error) {
+      return Response.json(
+        {
+          status: "error",
+          message: "authentication failed",
+          error: error.message,
+        },
+        {
+          status: 401,
+        }
+      );
+    }
+  }
+}
+```
+
+And pass this `config` to `MetabaseProvider`
+```ts
+const config = {
+  metabaseInstanceUrl: "https://metabase.example.com", // Required: Your Metabase instance URL
+  jwtProviderUri: "/sso/metabase", // Required: An endpoint in your app that returns signs the user in and delivers a token
+};
+```
+
+#### Using Pages Router
+
+This works almost the same as the App Router, but you don't need to mark your component that imports Metabase SDK components as a React Client component (with "use client").
+
+If you authenticate with Metabase using JWT, you can create an API route that signs a user into Metabase.
+
+Create a new `metabase.ts` file in your `pages/api/*` directory, for example `pages/api/sso/metabase.ts` that corresponds to an endpoint at /api/sso/metabase.
+
+```typescript
+import type { NextApiRequest, NextApiResponse } from "next";
+import jwt from "jsonwebtoken";
+
+const METABASE_JWT_SHARED_SECRET = process.env.METABASE_JWT_SHARED_SECRET || "";
+const METABASE_INSTANCE_URL = process.env.METABASE_INSTANCE_URL || "";
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+  const token = jwt.sign(
+    {
+      email: user.email,
+      first_name: user.firstName,
+      last_name: user.lastName,
+      groups: [user.group],
+      exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minutes expiration
+    },
+    // This is the JWT signing secret in your Metabase JWT authentication setting
+    METABASE_JWT_SHARED_SECRET
+  );
+  const ssoUrl = `${METABASE_INSTANCE_URL}/auth/sso?token=true&jwt=${token}`;
+
+  try {
+    const ssoResponse = await fetch(ssoUrl, { method: "GET" });
+    const ssoResponseBody = await ssoResponse.json();
+
+    res.status(200).json(ssoResponseBody);
+  } catch (error) {
+    if (error instanceof Error) {
+      res.status(401).json({
+        status: "error",
+        message: "authentication failed",
+        error: error.message,
+      });
+    }
+  }
+}
+```
+
+And pass this `config` to `MetabaseProvider`
+```ts
+const config = {
+  metabaseInstanceUrl: "https://metabase.example.com", // Required: Your Metabase instance URL
+  jwtProviderUri: "/api/sso/metabase", // Required: An endpoint in your app that returns signs the user in and delivers a token
+};
+```
+
 # Development
 
 ## Storybook
-- 
GitLab