├── .gitignore
├── LICENSE
├── README.md
├── components
└── nav.js
├── next.config.js
├── package.json
├── pages
├── _document.js
├── about.js
├── deep
│ └── p.js
└── index.js
├── public
├── example.png
└── favicon.ico
└── yarn.lock
/.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 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Dmitry
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Deploying Next.js site to IPFS
2 |
3 | 1. Inject `` into `
` at runtime:
4 |
5 | ```jsx
6 | // ./pages/_document.js
7 | import Document, { Html, Head, Main, NextScript } from 'next/document'
8 |
9 | const scriptTxt = `
10 | (function () {
11 | const { pathname } = window.location
12 | const ipfsMatch = /.*\\/Qm\\w{44}\\//.exec(pathname)
13 | const base = document.createElement('base')
14 |
15 | base.href = ipfsMatch ? ipfsMatch[0] : '/'
16 | document.head.append(base)
17 | })();
18 | `
19 |
20 | class MyDocument extends Document {
21 |
22 | render() {
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 | }
36 |
37 | export default MyDocument
38 | ```
39 |
40 | 2. All local resources must have relative links to make use of **base.href**
41 |
42 | ```jsx
43 |
44 | // or
45 |
46 | ```
47 |
48 | 3. Compiled JavaScript assets should also respect **base.href**:
49 |
50 | ```js
51 | // next.config.js
52 | module.exports = {
53 | assetPrefix: './',
54 | }
55 | ```
56 |
57 | 4. `Route.push|replace()` would normally override **location.pathname**, so need to provide a different **as** parameter.
58 | One way to do it is with a custom **Link** component:
59 |
60 | ```jsx
61 | import {resolve} from 'url'
62 |
63 | const BaseLink = ({href, as, ...rest}) => {
64 |
65 | const newAs = useMemo(() => {
66 | let baseURI_as = as || href
67 |
68 | // make absolute url relative
69 | // when displayed in url bar
70 | if (baseURI_as.startsWith('/')) {
71 | // for static html compilation
72 | baseURI_as = '.' + href
73 | // => About
74 |
75 | // on the client
76 | // document is unavailable when compiling on the server
77 | if (typeof document !== 'undefined') {
78 | baseURI_as = resolve(document.baseURI, baseURI_as)
79 | // => About
80 | }
81 | }
82 | return baseURI_as
83 | }, [as, href])
84 |
85 | return
86 | }
87 | ```
88 |
89 | This way a `` would lead to `https://gateway.ipfs.io/ipfs/Qm/about` when clicked.
90 |
91 | As IPFS doesn't support automatic redirect to index for 404 routes, which is commonly employed when hosting SPA, one may want `/about` route to lead to `https://gateway.ipfs.io/ipfs/Qm/about.html` file if that file was statically compiled. In that case there will be need for further **baseURI_as** modification beyond simple `baseURI_as += '.html'` if there's need to preserve hash and search queries, for example.
92 |
93 | Or better yet use `exportTrailingSlash: true` in `next.config.js`. Then `pages/about.js` will be compiled to `out/about/index.html` and `/about` route will be available at `https://gateway.ipfs.io/ipfs/Qm/about/` which will survide page reloads without need for redirect to index for 404 routes:
94 |
95 | ```js
96 | module.exports = {
97 | assetPrefix: './',
98 | exportTrailingSlash: true,
99 | }
100 | ```
101 |
102 | 5. Build and export html files
103 |
104 | ```sh
105 | yarn build && yarn export
106 | ```
107 |
108 | 6. Add output directory to ipfs
109 |
110 | ```sh
111 | ipfs add -r out
112 | ```
--------------------------------------------------------------------------------
/components/nav.js:
--------------------------------------------------------------------------------
1 | import React, {useMemo} from 'react'
2 | import Link from 'next/link'
3 | import {resolve} from 'url'
4 |
5 | const links = [
6 | { href: 'https://zeit.co/now', label: 'ZEIT' },
7 | { href: 'https://github.com/zeit/next.js', label: 'GitHub' },
8 | ].map(link => {
9 | link.key = `nav-link-${link.href}-${link.label}`
10 | return link
11 | })
12 |
13 | const BaseLink = ({href, as, ...rest}) => {
14 |
15 | const newAs = useMemo(() => {
16 | let baseURI_as = as || href
17 |
18 | // make absolute url relative
19 | // when displayed in url bar
20 | if (baseURI_as.startsWith('/')) {
21 | // for static html compilation
22 | baseURI_as = '.' + href
23 | // => About
24 |
25 | // on the client
26 | if (typeof document !== 'undefined') {
27 | baseURI_as = resolve(document.baseURI, baseURI_as)
28 | // => About
29 | }
30 | }
31 | return baseURI_as
32 | }, [as, href])
33 |
34 | return
35 | }
36 |
37 | const Nav = () => (
38 |
89 | )
90 |
91 | export default Nav
92 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | assetPrefix: './',
3 | exportTrailingSlash: true,
4 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ipfs-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "export": "next export"
10 | },
11 | "dependencies": {
12 | "next": "9.1.6",
13 | "react": "16.12.0",
14 | "react-dom": "16.12.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | // ./pages/_document.js
2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
3 |
4 | const scriptTxt = `
5 | (function () {
6 | const { pathname } = window.location
7 | const ipfsMatch = /.*\\/Qm\\w{44}\\//.exec(pathname)
8 | const base = document.createElement('base')
9 |
10 | base.href = ipfsMatch ? ipfsMatch[0] : '/'
11 | document.head.append(base)
12 | })();
13 | `
14 |
15 | class MyDocument extends Document {
16 |
17 | render() {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default MyDocument
--------------------------------------------------------------------------------
/pages/about.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 | import Nav from '../components/nav'
4 |
5 | const About = () => (
6 |
7 |
8 | About
9 |
10 |
11 |
12 |
13 |
14 |
15 |
About Page
16 |
17 | To get started, edit pages/index.js and save to reload.
18 |
19 |
20 |
21 |
22 |
23 | )
24 |
25 | export default About
26 |
--------------------------------------------------------------------------------
/pages/deep/p.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 | import Nav from '../../components/nav'
4 |
5 | const Deeper = () => (
6 |
7 |
8 | deep/p
9 |
10 |
11 |
12 |
13 |
14 |
15 |
Deeper Page
16 |
17 | To get started, edit pages/index.js and save to reload.
18 |
19 | {/* all resources must be linked relaviely ("example.png" or "./example.png") for base.href to take effect */}
20 |
21 |
22 |
23 | )
24 |
25 | export default Deeper
26 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 | import Nav from '../components/nav'
4 |
5 | const Home = () => (
6 |
7 |
8 | Home
9 |
10 |
11 |
12 |
13 |
14 |
15 |
Welcome to Next.js!
16 |
17 | To get started, edit pages/index.js and save to reload.
18 |