diff --git a/.prettierignore b/.prettierignore index 826c3bed..c78d4e5b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,5 @@ node_modules dist .npmignore CHANGELOG.md +examples/nextjs/.next +examples/nextjs/next.lock diff --git a/docs/guides/ssr-and-frameworks.md b/docs/guides/ssr-and-frameworks.md new file mode 100644 index 00000000..8305307b --- /dev/null +++ b/docs/guides/ssr-and-frameworks.md @@ -0,0 +1,73 @@ +# Frameworks and SSR + +The main thing to consider when using the library with Server-side Rendering (SSR) or a fullstack framework like Next.js or Remix is to make sure that +the map is excluded from Server-side Rendering since that is not supported by the Google Maps API. We are currently evaluating a solution that would provide basic SSR capabilities via the Static Maps API. + +## Next.js + +This is how a component in a Next.js (app router) application looks like. Checkout the example [code](https://github.com/visgl/react-google-maps/tree/main/examples/nextjs) on Github or play around with the [demo](https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/nextjs) on Codesandbox. + +:::note + +The `use client;` statement at the top tells Next.js that +this component should only be rendered on the client. + +::: + +```tsx +'use client'; + +import {APIProvider, Map} from '@vis.gl/react-google-maps'; + +export default function MyMap() { + return ( +
+ + + +
+ ); +} +``` + +## Remix + +Here is the best approach we found to use a map component in a Remix application. Checkout the example [code](https://github.com/visgl/react-google-maps/tree/main/examples/remix) on Github or play around with the [demo](https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/remix) on Codesandbox. + +Wrap the map in a `` component from the [`remix-utils`](https://github.com/sergiodxa/remix-utils) package for it to be rendered only on the client. + +:::note + +If you use a fallback and you know the dimensions of your final map, make sure that +the fallback has the same size to prevent layout shifts when the map component loads. + +::: + +```tsx +import {APIProvider, Map} from '@vis.gl/react-google-maps'; +import {ClientOnly} from 'remix-utils/client-only'; + +export default function MyMap() { + return ( + }> + {() => ( + + + + )} + + ); +} +``` diff --git a/docs/table-of-contents.json b/docs/table-of-contents.json index 6c2cd4b9..dcd020eb 100644 --- a/docs/table-of-contents.json +++ b/docs/table-of-contents.json @@ -16,6 +16,7 @@ "items": [ "guides/interacting-with-google-maps-api", "guides/deckgl-integration", + "guides/ssr-and-frameworks", "guides/writing-examples" ] }, diff --git a/examples/nextjs/.eslintrc.json b/examples/nextjs/.eslintrc.json new file mode 100644 index 00000000..37224185 --- /dev/null +++ b/examples/nextjs/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"] +} diff --git a/examples/nextjs/.example.env b/examples/nextjs/.example.env new file mode 100644 index 00000000..57df6474 --- /dev/null +++ b/examples/nextjs/.example.env @@ -0,0 +1,2 @@ +# Duplicate this file and rename it to ".env" and enter your Google Maps API key here +NEXT_PUBLIC_GOOGLE_MAPS_API_KEY= diff --git a/examples/nextjs/.gitignore b/examples/nextjs/.gitignore new file mode 100644 index 00000000..28be9ded --- /dev/null +++ b/examples/nextjs/.gitignore @@ -0,0 +1,51 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# Ignoring tsconfig.json here since NextJS does not provide a way yet to specify a custom tsconfig.json path +# and rather hard codes the tsconfig.json path. We do want to use different tsconfig files +# for this example to be able to use the local files in dev mode and the installed package +# when called in Codesandbox. +tsconfig.json + +# Ignoring the lock folder here since this is not intended +# for a production build This should not be done on a real project. +# https://nextjs.org/docs/app/api-reference/next-config-js/urlImports#lockfile +next.lock diff --git a/examples/nextjs/README.md b/examples/nextjs/README.md new file mode 100644 index 00000000..d831f7c2 --- /dev/null +++ b/examples/nextjs/README.md @@ -0,0 +1,41 @@ +# Next.js Example + +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). It shows a basic map setup with two routes. + +## Demo + +Checkout the [demo](https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/nextjs) on Codesandbox. + +## Google Maps Platform API Key + +This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. +See [the official documentation][get-api-key] on how to create and configure your own key. + +The API key has to be provided via an environment variable `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY`. This can be done by creating a +file named `.env` in the example directory with the following content: + +```shell title=".env" +NEXT_PUBLIC_GOOGLE_MAPS_API_KEY="" +``` + +If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) + +## Development + +First, run the development server: + +For the local server that uses the local library files: + +```bash +npm run start-local +``` + +For the regular dev server that uses the installe files for `@vis.gl/react-google-maps`: + +```bash +npm run start +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key diff --git a/examples/nextjs/next.config.ts b/examples/nextjs/next.config.ts new file mode 100644 index 00000000..1dc18dc7 --- /dev/null +++ b/examples/nextjs/next.config.ts @@ -0,0 +1,7 @@ +import type {NextConfig} from 'next'; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json new file mode 100644 index 00000000..6cc6a8e2 --- /dev/null +++ b/examples/nextjs/package.json @@ -0,0 +1,23 @@ +{ + "scripts": { + "start": "cp tsconfig.sandbox.json tsconfig.json && next dev", + "start-local": "cp tsconfig.local.json tsconfig.json && next dev", + "build": "cp tsconfig.sandbox.json tsconfig.json && next build", + "serve": "next start", + "lint": "next lint" + }, + "dependencies": { + "@vis.gl/react-google-maps": "latest", + "react": "19.0.0-rc-66855b96-20241106", + "react-dom": "19.0.0-rc-66855b96-20241106", + "next": "15.0.3" + }, + "devDependencies": { + "typescript": "^5", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "15.0.3" + } +} diff --git a/examples/nextjs/src/app/about/page.module.css b/examples/nextjs/src/app/about/page.module.css new file mode 100644 index 00000000..cdffddd5 --- /dev/null +++ b/examples/nextjs/src/app/about/page.module.css @@ -0,0 +1,8 @@ +.page { + width: 100%; + height: 100%; + background: aliceblue; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/examples/nextjs/src/app/about/page.tsx b/examples/nextjs/src/app/about/page.tsx new file mode 100644 index 00000000..02b50f87 --- /dev/null +++ b/examples/nextjs/src/app/about/page.tsx @@ -0,0 +1,5 @@ +import styles from './page.module.css'; + +export default function About() { + return
About
; +} diff --git a/examples/nextjs/src/app/components/header.module.css b/examples/nextjs/src/app/components/header.module.css new file mode 100644 index 00000000..fdd0c627 --- /dev/null +++ b/examples/nextjs/src/app/components/header.module.css @@ -0,0 +1,18 @@ +.container { + width: 100%; + height: 100%; +} + +.header { + display: flex; + align-items: center; + justify-content: center; +} + +.nav ul { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + list-style: none; +} diff --git a/examples/nextjs/src/app/components/header.tsx b/examples/nextjs/src/app/components/header.tsx new file mode 100644 index 00000000..c3c3fd6b --- /dev/null +++ b/examples/nextjs/src/app/components/header.tsx @@ -0,0 +1,20 @@ +import Link from 'next/link'; + +import styles from './header.module.css'; + +export default function Header() { + return ( +
+ +
+ ); +} diff --git a/examples/nextjs/src/app/globals.css b/examples/nextjs/src/app/globals.css new file mode 100644 index 00000000..bca01b24 --- /dev/null +++ b/examples/nextjs/src/app/globals.css @@ -0,0 +1,11 @@ +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} diff --git a/examples/nextjs/src/app/layout.module.css b/examples/nextjs/src/app/layout.module.css new file mode 100644 index 00000000..2f20a005 --- /dev/null +++ b/examples/nextjs/src/app/layout.module.css @@ -0,0 +1,7 @@ +.container { + width: 100vw; + height: 100vh; + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 60px 1fr; +} diff --git a/examples/nextjs/src/app/layout.tsx b/examples/nextjs/src/app/layout.tsx new file mode 100644 index 00000000..bc381f59 --- /dev/null +++ b/examples/nextjs/src/app/layout.tsx @@ -0,0 +1,33 @@ +import type {Metadata} from 'next'; +import Script from 'next/script'; + +import Header from './components/header'; + +import styles from './layout.module.css'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'React Google Maps - NextJS Example' +}; + +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + +
+
+ {children} +
+ +