個人的に開発していてめっちゃ苦戦したのでメモ。SSRのやり方はわかっていることを前提に書きます
具体的に
リロード時(クライアントナビゲーションではなく)にサーバー側に cookie 情報が送られないという問題に遭遇しました。
前提条件
Apollo Client を使用して、初期ロード時にデータのキャッシュを作成している(SSR)
解決
とりあえず、@apollo/react-hooks
を使ってるなら、getDataFromTree
は使えません。どこで読んだかは覚えていませんが、getDataFromTree
はdeprecatedらしく、代わりに @apollo/react-ssr
がエキスポートするgetMarkupFromTree
を使えとこのとです( https://github.com/apollographql/react-apollo/issues/3251 )。
Apollo Clientのサーバー initialize めんどくさいんで書きたくなかったんですが、https://github.com/lfades/next-with-apollo が全く更新されていなくて使えないので、ソース見ながらほぼパクって getDataFromTree
をgetMarkupFromTree
に変えました
// withApollo.js
import React from "react";
import Head from "next/head";
import "isomorphic-unfetch";
import { renderToString } from "react-dom/server";
import { getMarkupFromTree } from "@apollo/react-ssr";
let _apolloClient = null;
// same implementation with next-with-apollo,
// just subtituting getDataFromTree -> getMarkupFromTree for hook support
const getClient = (clientFn, options = {}) => {
return clientFn(options);
};
const initApollo = (clientFn, options) => {
if (!clientFn) {
throw new Error(
"[withApollo] the first param is missing and is required to get the ApolloClient"
);
}
if (!process.browser) {
return getClient(clientFn, options);
}
if (!_apolloClient) {
_apolloClient = getClient(clientFn, options);
}
return _apolloClient;
};
export default function withApollo(client) {
return App => {
return class WithApollo extends React.Component {
constructor(props) {
super(props);
this.displayName = "withApollo(App)";
this.apollo =
props.apollo ||
initApollo(client, {
initialState: props.apolloState.data
});
}
static async getInitialProps(appCtx) {
const { AppTree, ctx, router } = appCtx;
const headers = ctx.req ? ctx.req.headers : {};
const apollo = initApollo(client, { ctx, headers });
const apolloState = {};
const getInitialProps = App.getInitialProps;
let appProps = {
pageProps: {}
};
if (getInitialProps) {
ctx.apolloClient = apollo;
appProps = await getInitialProps(appCtx);
}
if (ctx.res && (ctx.res.headersSent || ctx.res.finished)) {
return {};
}
if (!process.browser) {
try {
await getMarkupFromTree({
renderFunction: renderToString,
tree: (
<AppTree
{...appProps}
router={router}
apolloState={apolloState}
apollo={apollo}
/>
)
});
} catch (e) {
console.error("Error while running `getMarkupFromTree`", e);
}
Head.rewind();
apolloState.data = apollo.cache.extract();
}
apollo.toJSON = () => {
return null;
};
return {
...appProps,
apolloState,
apollo
};
}
render() {
return <App {...this.props} apollo={this.apollo} />;
}
};
};
}
ここまではいいのですが、SSR時に送りたいcookie情報をサーバーに送れないという問題にぶち当たりました。https://qiita.com/mizchi/items/0942be44dba68783a170 も参考にしました
以下解決コード
// initApolloClient.js
import { ApolloClient, InMemoryCache, HttpLink } from "apollo-boost";
import fetch from "isomorphic-unfetch";
import withApollo from "./withApollo";
import { endpoint } from "../config";
let apolloClient = null;
// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
// @ts-ignore
global.fetch = fetch;
}
function createApolloClient(options) {
// このメソッドがSSR時にcookieをアペンドしてくれる
const customFetch = headers => (uri, options) => {
options.headers.cookie = headers.cookie;
return fetch(uri, options);
};
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
connectToDevTools: process.browser,
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
// uri: "https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn", // Server URL (must be absolute)
uri: process.env.NODE_ENV === "development" ? endpoint : endpoint,
credentials: "include",
// SSRの時だけ。
fetch: !process.browser && customFetch(options.headers)
}),
cache: new InMemoryCache().restore(options.initialState || {})
});
}
function initApollo(options) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (!process.browser) {
return createApolloClient(options);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(options);
}
return apolloClient;
}
export default withApollo(initApollo);
あとは_app.js
でHOCを使う
import App from "next/app";
import Page from "../components/Page";
import { ApolloProvider } from "@apollo/react-hooks";
import initApolloClient from "../lib/initApolloClient";
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
// this exposes the query to the user
pageProps.query = ctx.query;
return { pageProps };
}
render() {
const { Component, apollo, pageProps } = this.props;
return (
<ApolloProvider client={apollo}>
<Page>
<Component {...pageProps} />
</Page>
</ApolloProvider>
);
}
}
export default initApolloClient(MyApp);
終わり