Shopify 嵌入式应用程序 - 使用 nextjs 无法加载不同的页面
Shopify embedded App - Loading different pages does not work using nextjs
我有以下设置,当使用 nextjs 路由器加载新页面时,它不起作用,因为新页面是空白的。似乎根本没有发生客户端或基于 iframe 的导航重定向。
我已成功使用 Polaris Link
组件从一个页面导航到另一个页面,但这似乎完全在 iframe 中重新加载了我的应用程序。我想使用客户端路由,甚至没有成功地遵循这个例子
我正在使用一个名为 useAppRoute
的自定义挂钩来挂钩 shopify-app-bridge 的历史记录,但我认为这不是我想要实现的目标的最佳方法。
_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 例子
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;
我已经尝试过对 RoutePropagator
使用类似的方法,但订阅实际上并没有在页面更改时可靠地触发。
我只是认为 Shopify 不会支持 NextJS - 特别是 CLI 工具的变化现在支持自定义构建,而不是使用 NextJS + Koa。
所以解决这个问题的方法首先是基本上实现 _app.js
、RoutePropigater
代码示例 https://github.com/carstenlebek/shopify-node-app-starter
另外,特别是,我还必须将我的节点包更新为与此入门包示例相同的版本。希望这对其他人有帮助
我有以下设置,当使用 nextjs 路由器加载新页面时,它不起作用,因为新页面是空白的。似乎根本没有发生客户端或基于 iframe 的导航重定向。
我已成功使用 Polaris Link
组件从一个页面导航到另一个页面,但这似乎完全在 iframe 中重新加载了我的应用程序。我想使用客户端路由,甚至没有成功地遵循这个例子
我正在使用一个名为 useAppRoute
的自定义挂钩来挂钩 shopify-app-bridge 的历史记录,但我认为这不是我想要实现的目标的最佳方法。
_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 例子
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;
我已经尝试过对 RoutePropagator
使用类似的方法,但订阅实际上并没有在页面更改时可靠地触发。
我只是认为 Shopify 不会支持 NextJS - 特别是 CLI 工具的变化现在支持自定义构建,而不是使用 NextJS + Koa。
所以解决这个问题的方法首先是基本上实现 _app.js
、RoutePropigater
代码示例 https://github.com/carstenlebek/shopify-node-app-starter
另外,特别是,我还必须将我的节点包更新为与此入门包示例相同的版本。希望这对其他人有帮助