Skip to content

Setup with Next.js (App Router)

This guide provides instructions on how to use webview-bridge in Next.js (App Router) applications.

Page Router

For page routing, apply the same approach: set up the Provider in _app.tsx and use it similarly.

What does it solve?

By design, webview-bridge relies on client-side rendering, which can lead to hydration mismatches due to fetching data from the connected app. Resolving hydration mismatches is challenging in such scenarios. This guide helps to use Providers client-side in an SSR environment, avoiding execution on the server, and enables the use of default hooks seamlessly.

Installation

sh
$ npm add @webview-bridge/react @webview-bridge/web
sh
$ pnpm add @webview-bridge/react @webview-bridge/web
sh
$ yarn add @webview-bridge/react @webview-bridge/web

createLinkBridgeProvider

providers/BridgeProvider.ts

tsx
"use client";
import { createLinkBridgeProvider } from "@webview-bridge/react";
import type { AppBridge } from ""; // Import the type 'appBridge' declared in native

export const { BridgeProvider, useBridgeStore, useBridgeStatus, useBridgeLoose, useBridgeEventListener } =
  createLinkBridgeProvider<AppBridge>({
    throwOnError: true,
    // For proper hydration, it is advisable to set up an `initialBridge`. This step can be omitted if necessary.
    initialBridge: {
      token: '',
      openInAppBrowser: async (url) => {
        alert('mocking: ' + url)
      }
    },
    onReady: () => {
      console.log("bridge is ready");
    },
  });

Create a BridgeProvider and related hooks by utilizing options similar to linkBridge as shown in the code above.

Layout Side

app/layout.tsx

tsx
import "./globals.css";

import type { Metadata } from "next";
import { Inter } from "next/font/google";

import { BridgeProvider } from "./providers/bridgeProvider";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>
        {/* Here */}
        <BridgeProvider>{children}</BridgeProvider>
      </body>
    </html>
  );
}

Wrap the BridgeProvider around the children in the base code.

Client Component

app/components/BridgeHome.tsx

tsx
"use client";
import { useBridgeStatus, useBridgeStore } from "@/providers/BridgeProvider";

function Count() {
  const count = useBridgeStore((state) => state.count);

  return <p>Native Count: {count}</p>;
}

function DataText() {
  const { text, setDataText } = useBridgeStore((state) => ({
    text: state.data.text,
    setDataText: state.setDataText,
  }));

  return (
    <div>
      <p>Native Data Text: {text}</p>
      <input
        type="text"
        value={text}
        onChange={(e) => setDataText(e.target.value)}
      />
    </div>
  );
}

export default function BridgeHome() {
  const { increase, openInAppBrowser } = useBridgeStore((state) => ({
    increase: state.increase,
    openInAppBrowser: state.openInAppBrowser,
  }));
  const { isNativeMethodAvailable, isWebViewBridgeAvailable } =
    useBridgeStatus();

  return (
    <div>
      <div>
        {`isWebViewBridgeAvailable: ${String(isWebViewBridgeAvailable)}`}
      </div>
      <h2>This is WebView</h2>

      <button
        onClick={() => {
          if (isNativeMethodAvailable("openInAppBrowser")) {
            openInAppBrowser("https://github.com/gronxb/webview-bridge");
          }
        }}
      >
        open InAppBrowser
      </button>

      <Count />
      <button onClick={() => increase()}>Increase from web</button>

      <DataText />
    </div>
  );
}

Utilize hooks from createLinkBridgeProvider for client-side functionality as shown above.

Page Side

app/page.tsx

tsx
import dynamic from "next/dynamic";
import { Suspense } from "react";
import BridgeHome from "./BridgeHome";

const Page = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <BridgeHome />
    </Suspense>
  );
};

export default Page;