Skip to content

Commit

Permalink
Fix 404 page generation + refactor all layouts SSG/SSR (gcms preset) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Vadorequest authored May 28, 2021
1 parent e31e3e0 commit 0ff0e5e
Show file tree
Hide file tree
Showing 51 changed files with 1,025 additions and 648 deletions.
261 changes: 148 additions & 113 deletions src/layouts/core/coreLayoutSSG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { StaticPath } from '@/app/types/StaticPath';
import { StaticPathsOutput } from '@/app/types/StaticPathsOutput';
import { StaticPropsInput } from '@/app/types/StaticPropsInput';
import { DEMO_LAYOUT_QUERY } from '@/common/gql/demoLayoutQuery';
import {
GetCoreLayoutStaticPaths,
GetCoreLayoutStaticPathsOptions,
} from '@/layouts/core/types/GetCoreLayoutStaticPaths';
import {
GetCoreLayoutStaticProps,
GetCoreLayoutStaticPropsOptions,
} from '@/layouts/core/types/GetCoreLayoutStaticProps';
import { SSGPageProps } from '@/layouts/core/types/SSGPageProps';
import {
APOLLO_STATE_PROP_NAME,
Expand Down Expand Up @@ -40,143 +48,170 @@ import {
GetStaticPropsResult,
} from 'next';

const fileLabel = 'layouts/demo/demoLayoutSSG';
const fileLabel = 'layouts/core/coreLayoutSSG';
const logger = createLogger({
fileLabel,
});

/**
* Only executed on the server side at build time.
* Computes all static paths that should be available for all SSG pages.
* Necessary when a page has dynamic routes and uses "getStaticProps", in order to build the HTML pages.
*
* You can use "fallback" option to avoid building all page variants and allow runtime fallback.
*
* Meant to avoid code duplication between pages sharing the same layout.
* Can be overridden for per-page customisation (e.g: deepmerge).
*
* XXX Core component, meant to be used by other layouts, shouldn't be used by other components directly.
* Returns a "getStaticPaths" function.
*
* @return Static paths that will be used by "getCoreStaticProps" to generate pages
*
* @see https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation
* @param options
*/
export const getCoreStaticPaths: GetStaticPaths<CommonServerSideParams> = async (context: GetStaticPathsContext): Promise<StaticPathsOutput> => {
const lang = DEFAULT_LOCALE;
const bestCountryCodes: string[] = [lang, resolveFallbackLanguage(lang)];
const gcmsLocales: string = prepareGraphCMSLocaleHeader(bestCountryCodes);
const dataset: StaticDataset | GraphCMSDataset = await getGraphcmsDataset(gcmsLocales);
const customer: StaticCustomer | Customer = dataset?.customer;
export const getCoreLayoutStaticPaths: GetCoreLayoutStaticPaths = (options?: GetCoreLayoutStaticPathsOptions) => {
const {
fallback = false,
} = options || {};

/**
* Only executed on the server side at build time.
* Computes all static paths that should be available for all SSG pages.
* Necessary when a page has dynamic routes and uses "getStaticProps", in order to build the HTML pages.
*
* You can use "fallback" option to avoid building all page variants and allow runtime fallback.
*
* Meant to avoid code duplication between pages sharing the same layout.
* Can be overridden for per-page customisation (e.g: deepmerge).
*
* XXX Core component, meant to be used by other layouts, shouldn't be used by other components directly.
*
* @return Static paths that will be used by "getCoreLayoutStaticProps" to generate pages
*
* @see https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation
*/
const getStaticPaths: GetStaticPaths<CommonServerSideParams> = async (context: GetStaticPathsContext): Promise<StaticPathsOutput> => {
const lang = DEFAULT_LOCALE;
const bestCountryCodes: string[] = [lang, resolveFallbackLanguage(lang)];
const gcmsLocales: string = prepareGraphCMSLocaleHeader(bestCountryCodes);
const dataset: StaticDataset | GraphCMSDataset = await getGraphcmsDataset(gcmsLocales);
const customer: StaticCustomer | Customer = dataset?.customer;

// Generate only pages for languages that have been allowed by the customer
const paths: StaticPath[] = map(customer?.availableLanguages, (availableLanguage: string): StaticPath => {
return {
params: {
locale: availableLanguage,
},
};
});

// Generate only pages for languages that have been allowed by the customer
const paths: StaticPath[] = map(customer?.availableLanguages, (availableLanguage: string): StaticPath => {
return {
params: {
locale: availableLanguage,
},
fallback,
paths,
};
});

return {
fallback: false,
paths,
};

return getStaticPaths;
};

/**
* Only executed on the server side at build time.
* Computes all static props that should be available for all SSG pages.
*
* Note that when a page uses "getStaticProps", then "_app:getInitialProps" is executed (if defined) but not actually used by the page,
* only the results from getStaticProps are actually injected into the page (as "SSGPageProps").
*
* Meant to avoid code duplication between pages sharing the same layout.
* Can be overridden for per-page customisation (e.g: deepmerge).
* Returns a "getStaticProps" function.
*
* XXX Core component, meant to be used by other layouts, shouldn't be used by other components directly.
* Disables redirecting to the 404 page when building the 404 page.
*
* @return Props (as "SSGPageProps") that will be passed to the Page component, as props (known as "pageProps" in _app).
*
* @see https://github.com/vercel/next.js/discussions/10949#discussioncomment-6884
* @see https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
* @param options
*/
export const getCoreStaticProps: GetStaticProps<SSGPageProps, CommonServerSideParams> = async (props: StaticPropsInput): Promise<GetStaticPropsResult<SSGPageProps>> => {
const customerRef: string = process.env.NEXT_PUBLIC_CUSTOMER_REF;
const preview: boolean = props?.preview || false;
const previewData: PreviewData = props?.previewData || null;
const hasLocaleFromUrl = !!props?.params?.locale;
const locale: string = hasLocaleFromUrl ? props?.params?.locale : DEFAULT_LOCALE; // If the locale isn't found (e.g: 404 page)
const lang: string = locale.split('-')?.[0];
const bestCountryCodes: string[] = [lang, resolveFallbackLanguage(lang)];
const gcmsLocales: string = prepareGraphCMSLocaleHeader(bestCountryCodes);
const i18nTranslations: I18nextResources = await getLocizeTranslations(lang);
// XXX This part is not using "getGraphcmsDataset" because I'm not sure how to return the "apolloClient" instance when doing so, as it'll be wrapped and isn't returned
// So, code is duplicated, but that works fine
const apolloClient: ApolloClient<NormalizedCacheObject> = initializeApollo();
const variables = {
customerRef,
};
const queryOptions = {
displayName: 'DEMO_LAYOUT_QUERY',
query: DEMO_LAYOUT_QUERY,
variables,
context: {
headers: {
'gcms-locales': gcmsLocales,
export const getCoreLayoutStaticProps: GetCoreLayoutStaticProps = (options?: GetCoreLayoutStaticPropsOptions): GetStaticProps<SSGPageProps, CommonServerSideParams> => {
const {
enable404Redirect = true,
} = options || {};

/**
* Only executed on the server side at build time.
* Computes all static props that should be available for all SSG pages.
*
* Note that when a page uses "getStaticProps", then "_app:getInitialProps" is executed (if defined) but not actually used by the page,
* only the results from getStaticProps are actually injected into the page (as "SSGPageProps").
*
* Meant to avoid code duplication between pages sharing the same layout.
* Can be overridden for per-page customisation (e.g: deepmerge).
*
* XXX Core component, meant to be used by other layouts, shouldn't be used by other components directly.
*
* @return Props (as "SSGPageProps") that will be passed to the Page component, as props (known as "pageProps" in _app).
*
* @see https://github.com/vercel/next.js/discussions/10949#discussioncomment-6884
* @see https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
*/
const getStaticProps: GetStaticProps<SSGPageProps, CommonServerSideParams> = async (props: StaticPropsInput): Promise<GetStaticPropsResult<SSGPageProps>> => {
const customerRef: string = process.env.NEXT_PUBLIC_CUSTOMER_REF;
const preview: boolean = props?.preview || false;
const previewData: PreviewData = props?.previewData || null;
const hasLocaleFromUrl = !!props?.params?.locale;
const locale: string = hasLocaleFromUrl ? props?.params?.locale : DEFAULT_LOCALE; // If the locale isn't found (e.g: 404 page)
const lang: string = locale.split('-')?.[0];
const bestCountryCodes: string[] = [lang, resolveFallbackLanguage(lang)];
const gcmsLocales: string = prepareGraphCMSLocaleHeader(bestCountryCodes);
const i18nTranslations: I18nextResources = await getLocizeTranslations(lang);
// XXX This part is not using "getGraphcmsDataset" because I'm not sure how to return the "apolloClient" instance when doing so, as it'll be wrapped and isn't returned
// So, code is duplicated, but that works fine
const apolloClient: ApolloClient<NormalizedCacheObject> = initializeApollo();
const variables = {
customerRef,
};
const queryOptions = {
displayName: 'DEMO_LAYOUT_QUERY',
query: DEMO_LAYOUT_QUERY,
variables,
context: {
headers: {
'gcms-locales': gcmsLocales,
},
},
},
};
};

const {
data,
errors,
loading,
networkStatus,
...rest
}: ApolloQueryResult<{
customer: Customer;
}> = await apolloClient.query(queryOptions);
const {
data,
errors,
loading,
networkStatus,
...rest
}: ApolloQueryResult<{
customer: Customer;
}> = await apolloClient.query(queryOptions);

if (errors) {
// eslint-disable-next-line no-console
console.error(errors);
throw new Error('Errors were detected in GraphQL query.');
}
if (errors) {
// eslint-disable-next-line no-console
console.error(errors);
throw new Error('Errors were detected in GraphQL query.');
}

const {
customer,
} = data || {}; // XXX Use empty object as fallback, to avoid app crash when destructuring, if no data is returned
const dataset = {
customer,
};
const {
customer,
} = data || {}; // XXX Use empty object as fallback, to avoid app crash when destructuring, if no data is returned
const dataset = {
customer,
};

// Do not serve pages using locales the customer doesn't have enabled (useful during preview mode and in development env)
if (enable404Redirect && !includes(customer?.availableLanguages, locale)) {
logger.warn(`Locale "${locale}" not enabled for this customer (allowed: "${customer?.availableLanguages}"), returning 404 page.`);

// Do not serve pages using locales the customer doesn't have enabled (useful during preview mode and in development env)
if (!includes(customer?.availableLanguages, locale)) {
logger.warn(`Locale "${locale}" not enabled for this customer (allowed: "${customer?.availableLanguages}"), returning 404 page.`);
return {
notFound: true,
};
}

return {
notFound: true,
// Props returned here will be available as page properties (pageProps)
props: {
[APOLLO_STATE_PROP_NAME]: getApolloState(apolloClient),
bestCountryCodes,
serializedDataset: serializeSafe(dataset),
customer,
customerRef,
i18nTranslations,
gcmsLocales,
hasLocaleFromUrl,
isReadyToRender: true,
isStaticRendering: true,
lang,
locale,
preview,
previewData,
},
};
}

return {
// Props returned here will be available as page properties (pageProps)
props: {
[APOLLO_STATE_PROP_NAME]: getApolloState(apolloClient),
bestCountryCodes,
serializedDataset: serializeSafe(dataset),
customer,
customerRef,
i18nTranslations,
gcmsLocales,
hasLocaleFromUrl,
isReadyToRender: true,
isStaticRendering: true,
lang,
locale,
preview,
previewData,
},
};
};

return getStaticProps;
};
Loading

0 comments on commit 0ff0e5e

Please sign in to comment.