In this guide we are going to setup a PWA using Dynamic and Vite, React & Typescript.

Full code example

You can find the full code from this example here.

Pre-requisites

  • Node installed
  • Boilerplate project created with Vite
npm create vite@latest dynamic-pwa -- --template react-ts

Dynamic Setup

With a basic project in place, we can now setup the Dynamic React SDK

Install Dynamic Packages

npm install @dynamic-labs/sdk-react-core @dynamic-labs/ethereum

Add the DynamicContextProvider

In the src/main.tsx file we can add the DynamicContextProvider with an environment ID

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";

// Dynamic SDK imports
import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <DynamicContextProvider
      settings={{
        environmentId: "96bae37d-7ed7-44aa-9081-79bb984508fe",
        walletConnectors: [EthereumWalletConnectors],
      }}
    >
      <App />
    </DynamicContextProvider>
  </React.StrictMode>
);

Add the DynamicWidget

Over in the App file, we are going to use the DynamicWidget for users to be able to authenticate using the Dynamic UI

Now, in the src/App.tsx file, we can add the DynamicWidget component

import { DynamicWidget } from "@dynamic-labs/sdk-react-core";

function App() {
  return (
    <DynamicWidget />
  );
}

export default App;

Great, now Dynamic is fully added and we can get start with the PWA setup!

PWA Setup

Install Dependencies

npm install -D vite-plugin-pwa

Setup PWA

For this app to became a PWA we will need to generate a manifest file with some extra icons that the OS will use.

First, we can update our vite.config.ts file with vite-plugin-pwa:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { VitePWA } from "vite-plugin-pwa";

export default defineConfig({
  publicDir: "public",
  plugins: [
    react(),
    VitePWA({
      registerType: "autoUpdate",
      devOptions: {
        enabled: true,
      },
      includeAssets: [
        "logo.png",
        "apple-touch-icon.png",
        "mask-icon.svg",
        "icon.svg",
      ],
      manifest: {
        name: "My Awesome DApp",
        short_name: "Dynamic PWA",
        description: "My Awesome DApp description",
        theme_color: "#ffffff",
        icons: [
          {
            src: "logo.png",
            sizes: "192x192",
            type: "image/png",
          },
          {
            purpose: "any maskable",
            sizes: "260x260",
            src: "/apple-touch-icon.png",
            type: "image/png",
          },
        ],
      },
    }),
  ],
});

We also need to add some images like logo.png and apple-touch-icon.png to our public to serve as the PWA icons.

Now we have to specify in the index.html file some basic information about the PWA:

So, add the following to the head tag

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Dynamic PWA</title>
  <meta name="description" content="My Awesome DApp description" />
  <link rel="icon" href="/logo.png" />
  <link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180" />
  <link rel="mask-icon" href="/logo.png" color="#FFFFFF" />
  <meta name="theme-color" content="#ffffff" />
  <link rel="manifest" href="/manifest.webmanifest" />
</head>

Add an Install Button

Let’s add an install button so users can install our PWA.

We can add a hook to src/useInstallPWA.ts that allows us to trigger the PWA installation:

import { useCallback } from "react";

let deferredPrompt: Event | null = null;

const handleBeforeInstallPrompt = (event: Event) => {
  event.preventDefault();

  deferredPrompt = event;
};

window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt);

export const useInstallPWA = () =>
  useCallback(() => {
    if (deferredPrompt) {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (deferredPrompt as any).prompt();
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      (deferredPrompt as any).userChoice.then(() => {
        deferredPrompt = null;
      });
    }
  }, []);

Then we can use useInstallPWA in the src/main.tsx file to prompt the user to install the PWA:

import { DynamicWidget } from "@dynamic-labs/sdk-react-core";
import { useInstallPWA } from "./useInstallPWA";

function App() {
  const installPWA = useInstallPWA();

  return (
    <>
      <DynamicWidget />

      <button onClick={installPWA}>Install PWA</button>
    </>
  );
}

export default App;

Perfect, now you can serve your DApp under https and install the PWA on your computer or phone, then authenticate with Dynamic!