Search code examples
reactjsnext.jsshopify-appnext-routershopify-app-bridge

Pages are reloaded instead of routed in shopify next js app


I followed Shopify's guide, until the end of 4th step, to develop a Next JS app and I've setup two pages (embedded app navigation), Home and Page1. Now, when I click to open both pages, the app is doing a reload instead of routing...

You can see here the flickering issue - https://youtu.be/45RvYgxC7C0

Any help on this would be very appreciated.

_app.js

import React from "react";

import App from "next/app";
import Head from "next/head";

import { AppProvider } from "@shopify/polaris";
import { Provider } from "@shopify/app-bridge-react";
import Cookies from "js-cookie";

import "@shopify/polaris/dist/styles.css";
import "../css/styles.css";

import lang from "@shopify/polaris/locales/en.json";

export default class MyApp extends App {
    render() {
        const { Component, pageProps } = this.props;
        const config = { apiKey: API_KEY, shopOrigin: Cookies.get("shopOrigin"), forceRedirect: true };

        return (
            <React.Fragment>
                <Head>
                    <title>My App</title>

                    <meta charSet="utf-8" />
                    <meta name="viewport" content="width=device-width, initial-scale=1" />

                    <link rel="icon" href="favicon.ico" />
                </Head>

                <Provider config={config}>
                    <AppProvider i18n={lang}>
                        <Component {...pageProps} />
                    </AppProvider>
                </Provider>
            </React.Fragment>
        );
    }
}

home.js

import React from "react";

import { Page, Layout, Card, FooterHelp, Link } from "@shopify/polaris";

export default function Home() {
    return (
        <Page title="Home">
            <Layout>
                <Layout.Section>
                    <Card title="Online store dashboard" sectioned>
                        <p>View a summary of your online store’s performance.</p>
                    </Card>
                </Layout.Section>

                <Layout.Section>
                    <FooterHelp>
                        Learn more about{" "}
                        <Link url="#" external>
                            our app
                        </Link>
                    </FooterHelp>
                </Layout.Section>
            </Layout>
        </Page>
    );
}

Page1.js

import React from "react";

import { Page, Layout, Card, FooterHelp, Link } from "@shopify/polaris";

export default function Page1() {
    return (
        <Page title="Page1">
            <Layout>
                <Layout.Section>
                    <Card title="Online store dashboard" sectioned>
                        <p>View a summary of your online store’s performance.</p>
                    </Card>
                </Layout.Section>

                <Layout.Section>
                    <FooterHelp>
                        Learn more about{" "}
                        <Link url="#" external>
                            our app
                        </Link>
                    </FooterHelp>
                </Layout.Section>
            </Layout>
        </Page>
    );
}

Solution

  • When using Shopify's app-bridge, it has a default behavior of navigating to a new route within the iframe that holds your app (and thus completely reloading the app), whereas React implements a client-side router.

    Shopify doesn't provide a 100% plug-and-play solution for using client-side routing, but they do make it pretty easy with their ClientRouter component.

    The examples on that page are for react-router, not Next.js's router, but the same idea applies to next/router.

    For example, a simple router component could look like:

    import {useEffect, useContext} from 'react';
    import Router, { useRouter } from "next/router";
    import { Context as AppBridgeContext } from "@shopify/app-bridge-react";
    import { Redirect } from "@shopify/app-bridge/actions";
    import { RoutePropagator as ShopifyRoutePropagator } from "@shopify/app-bridge-react";
    
    const RoutePropagator = () => {
      const router = useRouter(); 
      const { route } = router;
      const appBridge = React.useContext(AppBridgeContext);
    
      // Subscribe to appBridge changes - captures appBridge urls 
      // and sends them to Next.js router. Use useEffect hook to 
      // load once when component mounted
      useEffect(() => {
        appBridge.subscribe(Redirect.Action.APP, ({ path }) => {
          Router.push(path);
        });
      }, []);
    
      return appBridge && route ? (
        <ShopifyRoutePropagator location={route} app={appBridge} />
      ) : null;
    }
    
    export default RoutePropagator;
    

    After creating that component, drop it in the _app.js file inside the Shopify routers, for example:

    <Provider config={config}>
      <AppProvider i18n={translations}>
        <RoutePropagator />
        <ApolloProvider client={client}>
          // child components
        </ApolloProvider>
      </AppProvider>
    </Provider>
    

    When _app loads, it will now subscribe to changes from appBridge and let appBridge know to send a signal to the client rather than reload the entire iframe. If you apply any routing within the app, such as one page to another, it will also now update the browser's address bar.