Skip to content

Commit

Permalink
Attempt at SSG wit fallback example (not sure if it works in non-dev …
Browse files Browse the repository at this point in the history
…env)
  • Loading branch information
Vadorequest committed May 27, 2020
1 parent 5460665 commit d5e23bd
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 2 deletions.
8 changes: 7 additions & 1 deletion src/components/doc/NativeFeaturesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { jsx } from '@emotion/core';
import React from 'react';
import { Alert, Button, Card, CardBody, CardSubtitle, CardText, CardTitle } from 'reactstrap';
import useI18n from '../../hooks/useI18n';
import I18nLink from '../i18n/I18nLink';
import Cards from '../utils/Cards';
import ExternalLink from '../utils/ExternalLink';
Expand All @@ -15,6 +16,8 @@ type Props = {}
* @param props
*/
const NativeFeaturesSection: React.FunctionComponent<Props> = (props): JSX.Element => {
const { locale } = useI18n();

return (
<DocSection>
<h2>Next.js native features</h2>
Expand Down Expand Up @@ -117,7 +120,10 @@ const NativeFeaturesSection: React.FunctionComponent<Props> = (props): JSX.Eleme
<ExternalLink href={'https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required'}>
<Button color={'link'}>Learn more about <code>getStaticProps</code> with <code>fallback</code> option</Button>
</ExternalLink>
<I18nLink href={'/examples/native-features/products-with-ssg-and-fallback'}>
<I18nLink
href={'/examples/native-features/example-with-ssg-and-fallback/[albumId]'}
as={`/${locale}/examples/native-features/example-with-ssg-and-fallback/1`}
>
<Button color={'link'}>Example with <code>getStaticProps</code> and <code>fallback</code></Button>
</I18nLink>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import * as Sentry from '@sentry/node';
import { createLogger } from '@unly/utils-simple-logger';
import deepmerge from 'deepmerge';
import map from 'lodash.map';
import { GetStaticPaths, GetStaticProps, NextPage } from 'next';
import { NextRouter, useRouter } from 'next/router';
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
import React, { useState } from 'react';
import DefaultLayout from '../../../../../components/pageLayouts/DefaultLayout';
import withApollo from '../../../../../hocs/withApollo';
import { StaticParams } from '../../../../../types/nextjs/StaticParams';
import { StaticPath } from '../../../../../types/nextjs/StaticPath';
import { StaticPathsOutput } from '../../../../../types/nextjs/StaticPathsOutput';
import { StaticPropsInput } from '../../../../../types/nextjs/StaticPropsInput';
import { StaticPropsOutput } from '../../../../../types/nextjs/StaticPropsOutput';
import { OnlyBrowserPageProps } from '../../../../../types/pageProps/OnlyBrowserPageProps';
import { SSGPageProps } from '../../../../../types/pageProps/SSGPageProps';
import { getCommonStaticPaths, getCommonStaticProps } from '../../../../../utils/nextjs/SSG';
import { Alert } from 'reactstrap';

const fileLabel = 'pages/[locale]/examples/native-features/example-with-ssg-and-fallback/[albumId]';
const logger = createLogger({ // eslint-disable-line no-unused-vars,@typescript-eslint/no-unused-vars
label: fileLabel,
});

/**
* Only executed on the server side at build time.
*
* @return Props (as "SSGPageProps") that will be passed to the Page component, as props
*
* @see https://github.com/zeit/next.js/discussions/10949#discussioncomment-6884
* @see https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
*/
export const getStaticProps: GetStaticProps<SSGPageProps, StaticParams> = async (props: StaticPropsInput): Promise<StaticPropsOutput> => {
const commonStaticProps: StaticPropsOutput = await getCommonStaticProps(props);
console.log('getStaticProps.props', props);
const { params: { albumId } } = props;
const album = await fetch(`https://jsonplaceholder.typicode.com/albums/${albumId}`)
.then((response) => response.json())
.catch((error) => {
console.error(error); // eslint-disable-line no-console
Sentry.captureException(error);
});

return deepmerge(commonStaticProps, {
props: {
album,
albumId,
},
});
};

/**
* Only executed on the server side at build time
* Necessary when a page has dynamic routes and uses "getStaticProps"
*/
export const getStaticPaths: GetStaticPaths<StaticParams> = async (): Promise<StaticPathsOutput> => {
const commonStaticPaths: StaticPathsOutput = await getCommonStaticPaths();
const { paths } = commonStaticPaths;
const albumIdsToPreBuild = ['1']; // Only '/album-1-with-ssg-and-fallback' is generated at build time, other will be generated on-demand

map(albumIdsToPreBuild, (albumId: string): void => {
map(paths, (path: StaticPath) => {
path.params.albumId = albumId;
});
});

console.log('commonStaticPaths', commonStaticPaths)

return {
...commonStaticPaths,
fallback: true,
};
};

/**
* SSG pages are first rendered by the server (during static bundling)
* Then, they're rendered by the client, and gain additional props (defined in OnlyBrowserPageProps)
* Because this last case is the most common (server bundle only happens during development stage), we consider it a default
* To represent this behaviour, we use the native Partial TS keyword to make all OnlyBrowserPageProps optional
*
* Beware props in OnlyBrowserPageProps are not available on the server
*/
type Props = {
albumId: string;
album: any;
} & SSGPageProps<Partial<OnlyBrowserPageProps>>;

const ExampleWithSSGAndFallbackAlbumPage: NextPage<Props> = (props): JSX.Element => {
const { albumId, album } = props;
const router: NextRouter = useRouter();
const [hasUsedFallbackRendering] = useState<boolean>(router.isFallback);

console.debug('ExampleWithSSGAndFallbackAlbumPage.props', props);
console.debug('router.isFallback', router.isFallback);

return (
<DefaultLayout
{...props}
pageName={'example-with-ssg-and-fallback/[albumId]'}
headProps={{
title: `Album N°${albumId} (SSG, ${hasUsedFallbackRendering ? 'using fallback' : 'not using fallback'}) - Next Right Now`,
}}
>
<div>
<Alert color={'info'} tag={'div'}>
{
hasUsedFallbackRendering ? (
<p>
This page <b>has</b> used fallback rendering (it <b>hadn't</b> been generated previously).
</p>
) : (
<p>
This page <b>has not</b> used fallback rendering (it <b>had</b> been generated previously).
</p>
)
}
</Alert>

<h1>Album N°{albumId || 'Unknown'}</h1>
<div>
Title: {album?.title || 'Unknown'}<br />
User Id: {album?.userId || 'Unknown'}<br />
</div>
</div>

</DefaultLayout>
);
};

export default withApollo()(ExampleWithSSGAndFallbackAlbumPage);
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { StaticPropsInput } from '../../../../types/nextjs/StaticPropsInput';
import { StaticPropsOutput } from '../../../../types/nextjs/StaticPropsOutput';
import { OnlyBrowserPageProps } from '../../../../types/pageProps/OnlyBrowserPageProps';
import { SSGPageProps } from '../../../../types/pageProps/SSGPageProps';
import { getSamePageI18nUrl } from '../../../../utils/app/router';
import { createApolloClient } from '../../../../utils/gql/graphql';
import { SUPPORTED_LOCALES } from '../../../../utils/i18n/i18n';
import { getCommonStaticPaths, getCommonStaticProps } from '../../../../utils/nextjs/SSG';
Expand Down
1 change: 1 addition & 0 deletions src/types/nextjs/StaticParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
* @see next.config.js "experimental.redirects" section for url params
*/
export type StaticParams = {
albumId?: string; // Used by album-[albumId]-with-ssg-and-fallback page
locale?: string; // The first path of the url is the "locale"
};

0 comments on commit d5e23bd

Please sign in to comment.