I have the following setup, when loading a new page using the nextjs router it does not work as the new page is blank. There seems to be no client-side or iframe-based navigation redirection occurring at all.
I have been successful using the Polaris Link
components to navigate from page to page but that seems to completely reload my app in the iframe. I would like to use client-side routing and have even followed this example with no luck https://stackoverflow.com/a/63481122/671095
I am using a custom hook called useAppRoute
to hook into the history of shopify-app-bridge but I don't think that's the best approach for what I would like to achieve.
_app.js
import {
ApolloClient,
ApolloProvider,
ApolloLink,
HttpLink,
InMemoryCache,
} from "@apollo/client";
import App from "next/app";
import { AppProvider } from "@shopify/polaris";
import { Provider, useAppBridge } from "@shopify/app-bridge-react";
import { authenticatedFetch } from "@shopify/app-bridge-utils";
import { Redirect } from "@shopify/app-bridge/actions";
import "@shopify/polaris/build/esm/styles.css";
import translations from "@shopify/polaris/locales/en.json";
import RoutePropagator from "../components/RoutePropagator";
import { useAppRoute } from "src/hooks/useAppRoute";
import { ShopifySettingsProvider } from "src/contexts/ShopifySettings";
function userLoggedInFetch(app) {
const fetchFunction = authenticatedFetch(app);
return async (uri, options) => {
const response = await fetchFunction(uri, options);
if (
response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1"
) {
const authUrlHeader = response.headers.get(
"X-Shopify-API-Request-Failure-Reauthorize-Url"
);
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/auth`);
return null;
}
return response;
};
}
function MyProvider(props) {
const app = useAppBridge();
const client = new ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.split(
(operation) => operation.getContext().clientName === "shopify",
new HttpLink({
uri: "/graphql-shopify",
fetch: userLoggedInFetch(app),
fetchOptions: {
credentials: "include",
},
}),
new HttpLink({ uri: "/graphql" })
),
});
const { shop } = props;
return (
<ApolloProvider client={client}>
<ShopifySettingsProvider shop={shop}>
{props.children}
</ShopifySettingsProvider>
</ApolloProvider>
);
}
function PolarisLink({ url, children, external, ...rest }) {
if (external) {
return (
<a href={url} {...rest}>
{children}
</a>
);
}
const redirect = useAppRoute();
return (
<span
onClick={(e) => {
console.log("redirected");
e.preventDefault();
e.stopPropagation();
redirect(url);
}}
>
<a {...rest}>{children}</a>
</span>
);
}
class MyApp extends App {
render() {
const { Component, pageProps, host, shop } = this.props;
console.log(host);
console.log(shop);
return (
<AppProvider i18n={translations} linkComponent={PolarisLink}>
<Provider
config={{
apiKey: API_KEY,
host: host,
forceRedirect: true,
}}
>
{/* <ClientRouter /> */}
<RoutePropagator />
<MyProvider Component={Component}>
<Component {...pageProps} />
</MyProvider>
</Provider>
</AppProvider>
);
}
}
MyApp.getInitialProps = async ({ ctx }) => {
console.log(ctx);
return {
host: ctx.query.host,
};
};
export default MyApp;
useAppRoute.js
import { useRouter } from "next/router";
import { useAppBridge } from "@shopify/app-bridge-react";
import { History } from "@shopify/app-bridge/actions";
export function useAppRoute() {
const app = useAppBridge();
const router = useRouter();
const history = History.create(app);
return (path) => {
const [, asPath] = router.asPath.split("?");
const pagePath = path.replace(/\/\d+/g, "/[id]");
router.push(pagePath, `${path}?${asPath}`).then(() => {
history.dispatch(History.Action.REPLACE, path);
});
};
}
RoutePropigator.js
import React, {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 { asPath } = 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 && asPath ? (
<ShopifyRoutePropagator location={asPath} app={appBridge} />
) : null;
}
export default RoutePropagator;
index.js - router.push example
import React, { useState } from "react";
import Link from "next/link";
import {
Frame,
Page,
Layout,
EmptyState,
Button,
Card,
} from "@shopify/polaris";
import { ResourcePicker, TitleBar } from "@shopify/app-bridge-react";
import store from "store-js";
import ResourceListWithProducts from "../components/elements/ResourceList";
import Sidebar from "../components/Sidebar";
import { useRouter } from 'next/router'
const img = "https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg";
const Index = () => {
const router = useRouter()
const [open, setOpen] = useState(false);
// A constant that defines your app's empty state
const emptyState = !store.get("ids");
const handleSelection = (resources) => {
const idsFromResources = resources.selection.map((product) => product.id);
setOpen(false);
store.set("ids", idsFromResources);
};
return (
<Frame navigation={<Sidebar />}>
<Page>
<TitleBar />
<ResourcePicker
resourceType="Product"
showVariants={false}
open={open}
onSelection={(resources) => handleSelection(resources)}
onCancel={() => setOpen(false)}
/>
{emptyState ? ( // Controls the layout of your app's empty state
<Layout>
<EmptyState heading="Customise your product" image={img}>
<p>Add options to customise your product.<button onClick={() => router.push('/colours')}>Go to colours</button></p>
</EmptyState>
</Layout>
) : (
// Uses the new resource list that retrieves products by IDs
<ResourceListWithProducts />
)}
</Page>
</Frame>
);
};
export default Index;
So turns out the solution to resolving this was first to basically impliment the _app.js
, RoutePropigater
code example laid out here https://github.com/carstenlebek/shopify-node-app-starter
Also in particular, I had to also update my node packages to the same versions as this starter pack example. Hope this helps other people