- install nvm if not done yet
- use latest node version
nvm use node || nvm install node
-
create nextjs app in the parent folder
npx create-next-app@latest --typescript <project-name> # or pnpm create next-app -- --typescript <project-name>
-
pin nodejs version in the project
node -v > .nvmrc
-
remove the
package.json
andnode_modules/
rm package.json rm -rf node_modules
-
install pnpm globally
npm i -g pnpm
-
install dependencies
pnpm install
-
remove
.eslintrc.json
rm .eslintrc.json
-
install prettier
pnpm i -D prettier eslint-config-prettier eslint-plugin-prettier
-
add
.eslintrc.js
/** @type {import('eslint').Linter.Config} */ module.exports = { extends: ['next', 'prettier', 'plugin:prettier/recommended'], };
-
add
.prettierrc.js
/** @type {import('prettier').Config} */ module.exports = { tabWidth: 2, overrides: [ { files: '*.md', options: { tabWidth: 4, }, }, ], semi: true, singleQuote: true, printWidth: 80, trailingComma: 'es5', };
-
install jest and react-testing-library
pnpm i -D jest @jest/types @testing-library/react @testing-library/jest-dom
-
add
__tests__/
foldermkdir -p __tests__
-
add
__tests__/jest.config.js
const nextJest = require('next/jest'); const createJestConfig = nextJest({ dir: './', }); /** @type {import('@jest/types').Config.InitialOptions} */ const customJestConfig = { rootDir: '../', setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], moduleDirectories: ['node_modules', '<rootDir>/'], testRegex: '__tests__/.*\\.test\\.tsx?$', testEnvironment: 'jest-environment-jsdom', }; module.exports = createJestConfig(customJestConfig);
-
add
__tests__/jest.setup.ts
import '@testing-library/jest-dom/extend-expect';
-
add to
package.json
{ "scripts": { "test": "jest --config ./__tests__/jest.config.js", "test:watch": "jest --config ./__tests__/jest.config.js --watch" } }
-
install urql and nextjs bindings
pnpm i urql graphql next-urql react-is @urql/core @urql/exchange-graphcache pnpm i -D @urql/devtools
-
add
lib/urql/getUrqlClientOptions.ts
import { devtoolsExchange } from '@urql/devtools'; import { cacheExchange } from '@urql/exchange-graphcache'; import { NextUrqlClientConfig } from 'next-urql'; import { debugExchange, dedupExchange, fetchExchange } from 'urql'; import getIsClient from 'lib/utils/getIsClient'; const getUrqlClientOptions: NextUrqlClientConfig = (ssrCache) => { const isClient = typeof window !== 'undefined'; const isProd = process.env.NEXT_PUBLIC_ENV === 'production'; return { url: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT || '', exchanges: [ ...(isClient && !isProd ? [devtoolsExchange, debugExchange] : []), dedupExchange, cacheExchange({}), ssrCache, // ssrExchange has to come before fetchExchange fetchExchange, ], }; }; export default getUrqlClientOptions;
-
add graphql query, i.e.
graphql/query/userQuery.ts
export const USER_QUERY = ` query { post(id: 1) { id title body } } `;
-
instantiate graphql client in one of the
getStaticProps
orgetServerSideProps
methodsimport type { GetStaticProps } from 'next'; import getUrqlClientOptions from 'lib/urql/getUrqlClientOptions'; import { initUrqlClient } from 'next-urql'; import { USER_QUERY } from 'graphql/query/userQuery'; import { ssrExchange } from 'urql'; import { WithUrqlState } from 'next-urql'; export interface PageProps {} export interface StaticProps extends WithUrqlState, PageProps {} export const getStaticProps: GetStaticProps<StaticProps> = async () => { const ssrCache = ssrExchange({ isClient: false }); const urqlClientOption = getUrqlClientOptions(ssrCache); const client = initUrqlClient(urqlClientOption, false); await client?.query(USER_QUERY).toPromise(); return { props: { urqlState: ssrCache.extractData(), }, revalidate: 600, }; };
-
add
lib/urql/withStaticUrqlClient.ts
to wrap static generated pagesimport { withUrqlClient } from 'next-urql'; import getUrqlClientOptions from './getUrqlClientOptions'; const withStaticUrqlClient = withUrqlClient(getUrqlClientOptions, { neverSuspend: true, // don't use Suspense on server side ssr: false, // don't generate getInitialProps for the page staleWhileRevalidate: true, // tell client to do network-only data fetching again if the cached data is outdated }); export default withStaticUrqlClient;
-
wrap the page with
withStaticUrqlClient
import withStaticUrqlClient from 'lib/urql/withStaticUrqlClient'; // ... export default withStaticUrqlClient(Page);
-
install graphql codegen dependencies
pnpm i @graphql-typed-document-node/core pnpm i -D @graphql-codegen/cli @graphql-codegen/typed-document-node @graphql-codegen/typescript @graphql-codegen/typescript-operations
-
add
lib/graphql-codegen/codegen.yml
schema: <html-to-graphql-endpoint-or-path-to-server-graphql> documents: './graphql/**/*.graphql' # custom frontend query or mutation defined in graphql generates: ./graphql/generated.ts: plugins: - typescript - typescript-operations - typed-document-node config: fetcher: fetch
-
add to
package.json
{ "scripts": { "codegen": "graphql-codegen --config lib/grpahql-codegen/codegen.yml" } }
- choose either integration method below depending on the usage of Plasmic
Use plasmic as Headless API
-
install plasmic dependencies
pnpm i @plasmicapp/loader-nextjs
-
add plasmic client
import { initPlasmicLoader } from '@plasmicapp/loader-nextjs'; export const Plasmic = initPlasmicLoader({ projects: [ { id: process.env.PLASMIC_ID || '', token: process.env.PLASMIC_TOKEN || '', }, ], preview: true, // set false for production env });
-
remove
pages/index.tsx
-
add
pages/[[...catchall]].tsx
to generate all pages created in plasmic studioimport { ComponentRenderData, PlasmicComponent, PlasmicRootProvider, } from '@plasmicapp/loader-nextjs'; import { Plasmic } from 'lib/plasmic/plasmic'; import withStaticUrqlClient from 'lib/urql/withStaticUrqlClient'; import { GetStaticPaths, GetStaticProps, NextPage } from 'next'; import Error from 'next/error'; export interface PageProps { plasmicData?: ComponentRenderData; } /** * Use fetchPages() to fetch list of pages that have been created in Plasmic */ export const getStaticPaths: GetStaticPaths = async () => { const pages = await Plasmic.fetchPages(); return { paths: pages.map((page) => ({ params: { catchall: page.path.substring(1).split('/'), }, })), fallback: 'blocking', }; }; /** * For each page, pre-fetch the data we need to render it */ export const getStaticProps: GetStaticProps<PageProps> = async (ctx) => { const { catchall } = ctx.params ?? {}; const plasmicPath = typeof catchall === 'string' ? catchall : Array.isArray(catchall) ? `/${catchall.join('/')}` : '/'; const plasmicData = await Plasmic.maybeFetchComponentData(plasmicPath); if (plasmicData) { return { props: { plasmicData, }, revalidate: 300, }; } else { return { props: {}, }; } }; const CatchallPage: NextPage<PageProps> = ({ plasmicData }) => { if (!plasmicData || plasmicData.entryCompMetas.length === 0) { return <Error statusCode={404} />; } const pageMeta = plasmicData.entryCompMetas[0]; return ( // Pass in the data fetched in getStaticProps as prefetchedData <PlasmicRootProvider loader={Plasmic} prefetchedData={plasmicData}> { // plasmicData.entryCompMetas[0].name contains the name // of the component you fetched. } <PlasmicComponent component={pageMeta.name} /> </PlasmicRootProvider> ); }; export default withStaticUrqlClient(CatchallPage);
-
add
pages/plasmic-host.tsx
to preview the pages on plasmic studioimport { PlasmicCanvasHost } from '@plasmicapp/loader-nextjs'; import { Plasmic } from 'lib/plasmic/plasmic'; import withStaticUrqlClient from 'lib/urql/withStaticUrqlClient'; import Script from 'next/script'; const PlasmicHost = () => { return ( Plasmic && ( <div> <Script src="https://static1.plasmic.app/preamble.js" strategy="beforeInteractive" /> <PlasmicCanvasHost /> </div> ) ); }; export default withStaticUrqlClient(PlasmicHost);
-
configure plasmic studio project settings to preview the project using
http://localhost:3000/plasmic-host
Use plasmic to code generate components
-
install plasmic dependencies
pnpm i -g @plasmicapp/cli pnpm i @plasmicapp/react-web
-
authenticate plasmic cli
plasmic auth
-
create a project on plasmic studio if not yet done
-
note the project id in the url, i.e.
fyLUsDXMW8eJuJ9WwfAaag
inhttps://studio.plasmic.app/projects/fyLUsDXMW8eJuJ9WwfAaag,
-
sync plasmic studio project down as react components
plasmic sync -p <project-id>
-
continue designing and using the generated pages and components in
pages/
andcomponents/
folder using the default setup