import { Meta } from '@storybook/addon-docs/blocks';

<Meta
    title="Get started/Installation: Next.js"
    summary="How to install and set up Preply Design System in a Next.js application."
/>

# Installation: Next.js

This guide covers server-side rendering with both the Next.js Pages Router and App Router. Start
with the packages, global styles, translations, and providers from
[Installation: Client-side app](/docs/get-started-installation-client-side-app--docs), then replace the
client-only bundler and rendering setup with the Next.js configuration below.

See the complete
[Next.js App Router example project](https://github.com/preply/design-system/tree/main/examples/next-js).

## Shared Next.js configuration

Design System icons require SVG imports to resolve to default-exported React components. Install
the Next.js SVGR integration:

```bash
npm install --save-dev next-plugin-svgr
```

Design System packages also need to be transpiled by Next.js. Add the following configuration to
`next.config.mjs`:

```js
import withSvgr from 'next-plugin-svgr';

const nextConfig = withSvgr({
    transpilePackages: [
        '@preply/ds-core',
        '@preply/ds-core-types',
        '@preply/ds-media-icons',
        '@preply/ds-theme-tokyo-ui',
        '@preply/ds-visual-coverage-preply-component-names',
        '@preply/ds-web-core',
        '@preply/ds-web-lib',
        '@preply/ds-web-root',
    ],
});

export default nextConfig;
```

The examples below use:

- `ServerStyleSheet` to collect Design System critical CSS during server rendering
- `SSRProvider` to make that stylesheet and the application's hostname available to components
- `RootProvider` to install the theme, color scheme, portal, and tooltip infrastructure

Set the `hostname` passed to `SSRProvider` to the application's host without a protocol, for
example `www.preply.com`. Design System links use it to distinguish internal and external URLs.

## Pages Router

### Add the application providers

Wrap the application with `IntlProvider` and `RootProvider` in `pages/_app.tsx`. Use
`target="none"` because browser DOM elements such as `document.body` are not available during
server rendering. The theme classes are collected and added to `<body>` in the next step.

```tsx
import messages from '@preply/ds-i18n/locales/en.json';
import { RootProvider } from '@preply/ds-web-root';
import type { AppProps } from 'next/app';
import { IntlProvider } from 'react-intl';

export default function App({ Component, pageProps }: AppProps) {
    return (
        <IntlProvider locale="en" messages={messages}>
            <RootProvider theme="tokyo-ui" target="none">
                <Component {...pageProps} />
            </RootProvider>
        </IntlProvider>
    );
}
```

Merge the Design System locale with your application messages if the application has its own
translations.

### Collect and render critical styles

Create `pages/_document.tsx`. It creates a new stylesheet for every request, provides it while
Next.js renders the application, and adds the collected styles and theme classes to the document.
It also loads the Design System global stylesheet and scopes its typography with
`data-preply-ds-theme`.

```tsx
import { ServerStyleSheet } from '@preply/ds-web-core';
import { SSRProvider } from '@preply/ds-web-root';
import Document, {
    DocumentContext,
    DocumentInitialProps,
    Head,
    Html,
    Main,
    NextScript,
} from 'next/document';

type Props = DocumentInitialProps & {
    dsClassName: string;
};

export default class MyDocument extends Document<Props> {
    static async getInitialProps(context: DocumentContext): Promise<Props> {
        const stylesheet = new ServerStyleSheet();
        const renderPage = context.renderPage;

        context.renderPage = () =>
            renderPage({
                enhanceApp: App => props => (
                    <SSRProvider stylesheet={stylesheet} hostname={context.req?.headers.host}>
                        <App {...props} />
                    </SSRProvider>
                ),
            });

        const initialProps = await Document.getInitialProps(context);

        return {
            ...initialProps,
            dsClassName: stylesheet.getClassName(),
            styles: (
                <>
                    {initialProps.styles}
                    {stylesheet.getStyleElement()}
                </>
            ),
        };
    }

    render() {
        return (
            <Html lang="en">
                <Head>
                    <link rel="preconnect" href="https://static.preply.com" />
                    <link rel="stylesheet" href="https://static.preply.com/ds/global.css" />
                </Head>
                <body className={this.props.dsClassName} data-preply-ds-theme="tokyo-ui">
                    <Main />
                    <NextScript />
                </body>
            </Html>
        );
    }
}
```

Keep any existing `styles`, `<Head>` content, body classes, or attributes when integrating this
with a custom document.

## App Router

The App Router needs a client provider that uses `useServerInsertedHTML` to inject each render's
critical CSS into the server response.

### Make `react-intl` available to React Server Components

Add a small client proxy at `src/react-intl-proxy.tsx`:

```tsx
'use client';

export * from 'react-intl-original';
```

Then extend `next.config.mjs` from the shared setup with aliases for the original package and the
client proxy:

```js
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import withSvgr from 'next-plugin-svgr';

const dirname = path.dirname(fileURLToPath(import.meta.url));

const nextConfig = withSvgr({
    transpilePackages: [
        '@preply/ds-core',
        '@preply/ds-core-types',
        '@preply/ds-media-icons',
        '@preply/ds-theme-tokyo-ui',
        '@preply/ds-visual-coverage-preply-component-names',
        '@preply/ds-web-core',
        '@preply/ds-web-lib',
        '@preply/ds-web-root',
    ],
    webpack: config => {
        config.resolve.alias['react-intl-original'] = import.meta.resolve('react-intl');
        config.resolve.alias['react-intl'] = path.resolve(dirname, 'src/react-intl-proxy.tsx');
        return config;
    },
});

export default nextConfig;
```

### Create the client providers

Create `src/components/DesignSystemProviders.tsx`:

```tsx
'use client';

import messages from '@preply/ds-i18n/locales/en.json';
import { ServerStyleSheet } from '@preply/ds-web-core';
import { RootProvider, SSRProvider } from '@preply/ds-web-root';
import { useServerInsertedHTML } from 'next/navigation';
import { ReactNode, useState } from 'react';
import { IntlProvider } from 'react-intl';

export function DesignSystemProviders({ children }: { children: ReactNode }) {
    const [stylesheet] = useState(() => new ServerStyleSheet());

    useServerInsertedHTML(() => stylesheet.getStyleElement());

    return (
        <SSRProvider stylesheet={stylesheet} hostname={process.env.NEXT_PUBLIC_APP_HOSTNAME}>
            <IntlProvider locale="en" messages={messages}>
                <RootProvider theme="tokyo-ui">
                    <div data-preply-ds-theme="tokyo-ui">{children}</div>
                </RootProvider>
            </IntlProvider>
        </SSRProvider>
    );
}
```

Do not pass `document.body` to `RootProvider` in code that renders on the server. With no `target`,
`RootProvider` renders a themed wrapper that works during both server rendering and hydration.

### Add the providers and global styles to the root layout

Use the provider in `app/layout.tsx` and load the global stylesheet in `<head>`:

```tsx
import { DesignSystemProviders } from '../components/DesignSystemProviders';

export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
        <html lang="en">
            <head>
                <link rel="preconnect" href="https://static.preply.com" />
                <link rel="stylesheet" href="https://static.preply.com/ds/global.css" />
            </head>
            <body>
                <DesignSystemProviders>{children}</DesignSystemProviders>
            </body>
        </html>
    );
}
```

Component-specific global providers, such as `AlertBannerProvider`, belong inside
`DesignSystemProviders`, around `children`.
