├── .gitignore
├── README.md
├── components
└── stack
│ └── Stack.tsx
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── _app.tsx
├── about.module.css
├── about.tsx
├── api
│ └── hello.ts
├── index.module.css
└── index.tsx
├── public
├── favicon.ico
└── vercel.svg
├── screen.gif
├── styles
└── globals.css
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | .idea
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NextJS stack transitions
2 |
3 | This repo is an example of [NextJS](https://nextjs.org/) route transitions via a [Stack component](./components/stack/Stack.tsx)
4 | whose purpose is to render flexible "how" and "when" the playIn and playOut of each
5 | page component is played.
6 |
7 | The main goal is to have the same Stack logic existing on [@cher-ami/router](https://github.com/cher-ami/router)
8 | but on nextJS file system routing. Secondary, to be able to handle route transitions
9 | without a DOM bound animation library like React Spring. This example is
10 | animated with GSAP, but could be with any other libs.
11 |
12 |
13 |
14 |
15 |
16 | ## How it works
17 |
18 | When the user update the browser history by clicking on "About" link, the Stack
19 | will render two pages components:
20 |
21 | ```
22 | App
23 | |_ Stack
24 | |_ Home (prev page playOut)
25 | |_ About (current page playIn)
26 | ```
27 |
28 | When the transition is complete, prev page is unmount.
29 |
30 | ```
31 | App
32 | |_ Stack
33 | |_ About
34 | ```
35 |
36 | In order to do this, each page register for the parent Stack component a playIn and playOut
37 | function + the DOM root element via `useImperativeHandle`. The Stack can thus be able to access
38 | these properties.
39 |
40 | ```jsx
41 | const Home = forwardRef((props, handleRef) => {
42 | const $root = useRef(null)
43 | useImperativeHandle(handleRef, () => ({
44 | playIn: () =>
45 | gsap.timeline().fromTo(
46 | $root.current,
47 | { autoAlpha: 0 },
48 | { autoAlpha: 1 }
49 | ),
50 | playOut: () =>
51 | gsap.timeline().to($root.current, {
52 | autoAlpha: 0,
53 | }),
54 | $root: $root.current,
55 | }))
56 | })
57 | ```
58 |
59 | Otherwise, the root App component will manage the transitions function added to Stack component by props.
60 | We can now control the scenario with previous and current page components \o/
61 |
62 | ```jsx
63 | function App({ Component, pageProps }) {
64 | const custom = useCallback(
65 | ({ prev, current }) =>
66 | new Promise(async (resolve) => {
67 | // playOut prev page component
68 | if (prev) await prev.playOut?.()
69 | // when playOut is complete, playin new current page component
70 | await current.playIn?.()
71 | resolve()
72 | }),
73 | []
74 | )
75 | return (
76 |