diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 3de56475244941..00000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,133 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: circleci/node:8-browsers - working_directory: ~/repo - steps: - - checkout - - run: - name: Installing dependencies - command: yarn install --frozen-lockfile - - run: - name: Linting - command: yarn lint - - persist_to_workspace: - root: ~/repo - paths: ['.'] - test: - parallelism: 4 - docker: - - image: circleci/node:8-browsers - working_directory: ~/repo - steps: - - attach_workspace: - at: . - - run: - name: Tests - command: yarn testall $(circleci tests glob "test/**/*.test.*" | circleci tests split --split-by=timings --timings-type=classname) - environment: - JEST_JUNIT_OUTPUT: 'reports/junit/js-test-results.xml' - JEST_JUNIT_CLASSNAME: '{filepath}' - - store_test_results: - path: ~/repo/reports - test-ie11: - docker: - - image: circleci/node:8-browsers - working_directory: ~/repo - steps: - - attach_workspace: - at: . - - run: - name: Test in ie11 - command: 'if [[ ! -z $BROWSERSTACK_USERNAME ]]; then yarn testall test/integration/production/; else echo "Not running for PR"; fi' - environment: - BROWSERSTACK: 'true' - BROWSER_NAME: 'ie' - test-safari: - docker: - - image: circleci/node:8-browsers - working_directory: ~/repo - steps: - - attach_workspace: - at: . - - run: - name: Test in Safari - command: 'if [[ ! -z $BROWSERSTACK_USERNAME ]]; then yarn testall test/integration/production/; else echo "Not running for PR"; fi' - environment: - BROWSERSTACK: 'true' - BROWSER_NAME: 'safari' - test-firefox: - docker: - - image: circleci/node:8-browsers - working_directory: ~/repo - steps: - - attach_workspace: - at: . - - run: - name: Test in Firefox - command: 'if [[ ! -z $BROWSERSTACK_USERNAME ]]; then yarn testall test/integration/production/; else echo "Not running for PR"; fi' - environment: - BROWSERSTACK: 'true' - BROWSER_NAME: 'firefox' - deploy: - docker: - - image: circleci/node:8-browsers - working_directory: ~/repo - steps: - - attach_workspace: - at: . - - run: - name: Potentially save npm token - command: '([[ ! -z $NPM_TOKEN ]] && echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc) || echo "Did not write npm token"' - - run: - name: Potentially publish canary release - command: 'if ls ~/.npmrc >/dev/null 2>&1 && [[ $(git describe --exact-match 2> /dev/null || :) =~ -canary ]]; then yarn run lerna publish from-git --npm-tag canary --yes; else echo "Did not publish"; fi' - - run: - name: Potentially publish stable release - command: 'if ls ~/.npmrc >/dev/null 2>&1 && [[ ! $(git describe --exact-match 2> /dev/null || :) =~ -canary ]]; then yarn run lerna publish from-git --yes; else echo "Did not publish"; fi' -workflows: - version: 2 - build-test-and-deploy: - jobs: - - build - - test: - requires: - - build - - test-ie11: - requires: - - build - filters: - branches: - only: - - master - - canary - - test-safari: - requires: - - build - - test - - test-ie11 - filters: - branches: - only: - - master - - canary - - test-firefox: - requires: - - build - - test - - test-ie11 - - test-safari - filters: - branches: - only: - - master - - canary - - deploy: - requires: - - test - filters: - branches: - only: - - master - - canary diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000000000..02dd2b05059b4c --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +node_modules +**/.next/** +**/_next/** +**/dist/** +examples/with-ioc/** +examples/with-kea/** \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000000000..e0e871115f83c4 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,226 @@ +{ + "root": true, + "parser": "babel-eslint", + "plugins": ["react", "react-hooks"], + "env": { + "browser": true, + "commonjs": true, + "es6": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "settings": { + "react": { + "version": "detect" + } + }, + "overrides": [ + { "files": ["**/__tests__/**"], "env": { "jest": true } }, + { + "files": ["**/*.ts", "**/*.tsx"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + }, + "warnOnUnsupportedTypeScriptVersion": false + }, + "plugins": ["@typescript-eslint"], + "rules": { + // Already handled by TS + "no-dupe-class-members": "off", + "no-undef": "off", + + // Add TypeScript specific rules (and turn off ESLint equivalents) + "@typescript-eslint/consistent-type-assertions": "warn", + "no-array-constructor": "off", + "@typescript-eslint/no-array-constructor": "warn", + "@typescript-eslint/no-namespace": "error", + "no-use-before-define": "off", + "@typescript-eslint/no-use-before-define": [ + "warn", + { + "functions": false, + "classes": false, + "variables": false, + "typedefs": false + } + ], + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "none", + "ignoreRestSiblings": true + } + ], + "no-unused-expressions": "off", + "@typescript-eslint/no-unused-expressions": [ + "error", + { + "allowShortCircuit": true, + "allowTernary": true, + "allowTaggedTemplates": true + } + ], + "no-useless-constructor": "off", + "@typescript-eslint/no-useless-constructor": "warn" + } + }, + { + "files": [ + "test/**/*", + "examples/**/*", + "packages/create-next-app/templates/**/*" + ], + "rules": { "react/react-in-jsx-scope": "off" } + } + ], + "rules": { + "array-callback-return": "warn", + "default-case": ["warn", { "commentPattern": "^no default$" }], + "dot-location": ["warn", "property"], + "eqeqeq": ["warn", "smart"], + "new-parens": "warn", + "no-array-constructor": "warn", + "no-caller": "warn", + "no-cond-assign": ["warn", "except-parens"], + "no-const-assign": "warn", + "no-control-regex": "warn", + "no-delete-var": "warn", + "no-dupe-args": "warn", + "no-dupe-class-members": "warn", + "no-dupe-keys": "warn", + "no-duplicate-case": "warn", + "no-empty-character-class": "warn", + "no-empty-pattern": "warn", + "no-eval": "warn", + "no-ex-assign": "warn", + "no-extend-native": "warn", + "no-extra-bind": "warn", + "no-extra-label": "warn", + "no-fallthrough": "warn", + "no-func-assign": "warn", + "no-implied-eval": "warn", + "no-invalid-regexp": "warn", + "no-iterator": "warn", + "no-label-var": "warn", + "no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }], + "no-lone-blocks": "warn", + "no-loop-func": "warn", + "no-mixed-operators": [ + "warn", + { + "groups": [ + ["&", "|", "^", "~", "<<", ">>", ">>>"], + ["==", "!=", "===", "!==", ">", ">=", "<", "<="], + ["&&", "||"], + ["in", "instanceof"] + ], + "allowSamePrecedence": false + } + ], + "no-multi-str": "warn", + "no-native-reassign": "warn", + "no-negated-in-lhs": "warn", + "no-new-func": "warn", + "no-new-object": "warn", + "no-new-symbol": "warn", + "no-new-wrappers": "warn", + "no-obj-calls": "warn", + "no-octal": "warn", + "no-octal-escape": "warn", + "no-redeclare": ["warn", { "builtinGlobals": false }], + "no-regex-spaces": "warn", + "no-restricted-syntax": ["warn", "WithStatement"], + "no-script-url": "warn", + "no-self-assign": "warn", + "no-self-compare": "warn", + "no-sequences": "warn", + "no-shadow-restricted-names": "warn", + "no-sparse-arrays": "warn", + "no-template-curly-in-string": "error", + "no-this-before-super": "warn", + "no-throw-literal": "warn", + "no-undef": "error", + "no-unexpected-multiline": "warn", + "no-unreachable": "warn", + "no-unused-expressions": [ + "error", + { + "allowShortCircuit": true, + "allowTernary": true, + "allowTaggedTemplates": true + } + ], + "no-unused-labels": "warn", + "no-unused-vars": [ + "warn", + { + "args": "none", + "ignoreRestSiblings": true + } + ], + "no-use-before-define": [ + "warn", + { + "functions": false, + "classes": false, + "variables": false + } + ], + "no-useless-computed-key": "warn", + "no-useless-concat": "warn", + "no-useless-constructor": "warn", + "no-useless-escape": "warn", + "no-useless-rename": [ + "warn", + { + "ignoreDestructuring": false, + "ignoreImport": false, + "ignoreExport": false + } + ], + "no-with": "warn", + "no-whitespace-before-property": "warn", + "react-hooks/exhaustive-deps": "warn", + "require-yield": "warn", + "rest-spread-spacing": ["warn", "never"], + "strict": ["warn", "never"], + "unicode-bom": ["warn", "never"], + "use-isnan": "warn", + "valid-typeof": "warn", + "getter-return": "warn", + "react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }], + "react/jsx-no-comment-textnodes": "warn", + "react/jsx-no-duplicate-props": "warn", + "react/jsx-no-target-blank": "warn", + "react/jsx-no-undef": "error", + "react/jsx-pascal-case": [ + "warn", + { + "allowAllCaps": true, + "ignore": [] + } + ], + "react/jsx-uses-react": "warn", + "react/jsx-uses-vars": "warn", + "react/no-danger-with-children": "warn", + "react/no-deprecated": "warn", + "react/no-direct-mutation-state": "warn", + "react/no-is-mounted": "warn", + "react/no-typos": "error", + "react/react-in-jsx-scope": "error", + "react/require-render-return": "error", + "react/style-prop-object": "warn", + "react-hooks/rules-of-hooks": "error" + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f08ecdf2c734f3..af2e4246fca4f0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,8 +1,9 @@ # Learn how to add code owners here: # https://help.github.com/en/articles/about-code-owners -/packages/ @timneutkens @Timer @dav-is @ijjk @lfades -/examples/ @kheruc @lfades -/test/ @timneutkens @Timer @dav-is @ijjk @lfades -/bench/ @timneutkens @dav-is -/errors/ @kheruc +* @Timer +/packages/ @timneutkens @Timer @ijjk @lfades +/examples/ @lfades @Timer +/test/ @timneutkens @Timer @ijjk @lfades +/bench/ @timneutkens @Timer +/errors/ @Timer diff --git a/.github/ISSUE_TEMPLATE/1.Bug_report.md b/.github/ISSUE_TEMPLATE/1.Bug_report.md index 6172fe14ec4825..ba86015e91e3ce 100644 --- a/.github/ISSUE_TEMPLATE/1.Bug_report.md +++ b/.github/ISSUE_TEMPLATE/1.Bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Create a bug report for the Next.js core +about: Create a bug report for the Next.js core / examples --- # Bug report @@ -12,21 +12,25 @@ A clear and concise description of what the bug is. ## To Reproduce Steps to reproduce the behavior, please provide code snippets or a repository: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error ## Expected behavior + A clear and concise description of what you expected to happen. ## Screenshots + If applicable, add screenshots to help explain your problem. ## System information - - OS: [e.g. macOS, Windows] - - Browser (if applies) [e.g. chrome, safari] - - Version of Next.js: [e.g. 6.0.2] + +- OS: [e.g. macOS, Windows] +- Browser (if applies) [e.g. chrome, safari] +- Version of Next.js: [e.g. 6.0.2] ## Additional context diff --git a/.github/ISSUE_TEMPLATE/2.Feature_request.md b/.github/ISSUE_TEMPLATE/2.Feature_request.md index 6d6da1091c57da..92a257d1a7e805 100644 --- a/.github/ISSUE_TEMPLATE/2.Feature_request.md +++ b/.github/ISSUE_TEMPLATE/2.Feature_request.md @@ -6,13 +6,17 @@ about: Create a feature request for the Next.js core # Feature request ## Is your feature request related to a problem? Please describe. + A clear and concise description of what you want and what your use case is. ## Describe the solution you'd like + A clear and concise description of what you want to happen. ## Describe alternatives you've considered + A clear and concise description of any alternative solutions or features you've considered. ## Additional context + Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/3.Example_Bug_report.md b/.github/ISSUE_TEMPLATE/3.Example_Bug_report.md deleted file mode 100644 index be1f0d71892d1a..00000000000000 --- a/.github/ISSUE_TEMPLATE/3.Example_Bug_report.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug report for examples -about: Create a bug report for one of the Next.js examples ---- - -# Examples bug report - -## Example name -Provide the example name - -## Describe the bug -A clear and concise description of what the bug is. - -## To Reproduce -Steps to reproduce the behavior, please provide code snippets or a repository: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -## Expected behavior -A clear and concise description of what you expected to happen. - -## Screenshots -If applicable, add screenshots to help explain your problem. - -## System information - - OS: [e.g. macOS, Windows] - - Browser (if applies) [e.g. chrome, safari] - - Version of Next.js: [e.g. 6.0.2] - -## Additional context - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/8.Question_about_next.md b/.github/ISSUE_TEMPLATE/8.Question_about_next.md deleted file mode 100644 index e51fa55a0f239a..00000000000000 --- a/.github/ISSUE_TEMPLATE/8.Question_about_next.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: Question about Next.js -about: If you have a question related to Next.js or the examples. Reach out to the community on https://spectrum.chat/next-js ---- - -# Question about Next.js - -Questions should be posted on https://spectrum.chat/next-js diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000000..3ea766fd6d3a13 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/zeit/next.js/discussions + about: Ask questions and discuss with other community members diff --git a/.github/main.workflow b/.github/main.workflow deleted file mode 100644 index 77d003f798ac49..00000000000000 --- a/.github/main.workflow +++ /dev/null @@ -1,14 +0,0 @@ -workflow "Generate pull request stats" { - on = "pull_request" - resolves = ["PR Stats"] -} - -workflow "Generate release stats" { - on = "release" - resolves = ["PR Stats"] -} - -action "PR Stats" { - uses = "zeit/next-stats-action@master" - secrets = ["GITHUB_TOKEN"] -} diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml new file mode 100644 index 00000000000000..8bc0e91b762b38 --- /dev/null +++ b/.github/workflows/build_test_deploy.yml @@ -0,0 +1,131 @@ +on: + push: + branches: [canary] + pull_request: + types: [opened, synchronize] + +name: Build, test, and deploy + +jobs: + build: + runs-on: ubuntu-latest + env: + NEXT_TELEMETRY_DISABLED: 1 + steps: + - uses: actions/checkout@v2 + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - run: yarn install --frozen-lockfile --check-files + - uses: actions/cache@v1 + id: cache-build + with: + path: '.' + key: ${{ github.sha }} + + lint: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/cache@v1 + id: restore-build + with: + path: '.' + key: ${{ github.sha }} + - run: yarn lint + + testAll: + name: Test All + runs-on: ubuntu-latest + needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + HEADLESS: true + strategy: + fail-fast: false + matrix: + group: [1, 2, 3, 4, 5, 6] + steps: + - uses: actions/cache@v1 + id: restore-build + with: + path: '.' + key: ${{ github.sha }} + + # TODO: remove after we fix watchpack watching too much + - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + + - run: node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 + + testsPass: + name: thank you, next + runs-on: ubuntu-latest + needs: [lint, testAll] + steps: + - run: exit 0 + + testFirefox: + name: Test Firefox (production) + runs-on: ubuntu-latest + needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + HEADLESS: true + steps: + - uses: actions/cache@v1 + id: restore-build + with: + path: '.' + key: ${{ github.sha }} + - run: yarn testfirefox --forceExit test/integration/production/ + + testSafari: + name: Test Safari (production) + runs-on: ubuntu-latest + needs: build + env: + BROWSERSTACK: true + NEXT_TELEMETRY_DISABLED: 1 + SKIP_LOCAL_SELENIUM_SERVER: true + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + steps: + - uses: actions/cache@v1 + id: restore-build + with: + path: '.' + key: ${{ github.sha }} + - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || yarn testsafari --forceExit test/integration/production/' + + testSafariOld: + name: Test Safari 10.1 (nav) + runs-on: ubuntu-latest + needs: [build, testSafari] + env: + BROWSERSTACK: true + LEGACY_SAFARI: true + NEXT_TELEMETRY_DISABLED: 1 + SKIP_LOCAL_SELENIUM_SERVER: true + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + steps: + - uses: actions/cache@v1 + id: restore-build + with: + path: '.' + key: ${{ github.sha }} + - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || yarn testsafari --forceExit test/integration/production-nav/' + + publishRelease: + name: Potentially publish release + runs-on: ubuntu-latest + needs: [testsPass] + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + steps: + - uses: actions/cache@v1 + id: restore-build + with: + path: '.' + key: ${{ github.sha }} + + - run: ./publish-release.sh diff --git a/.github/workflows/pull_request_stats.yml b/.github/workflows/pull_request_stats.yml new file mode 100644 index 00000000000000..d348c31fd9bc7a --- /dev/null +++ b/.github/workflows/pull_request_stats.yml @@ -0,0 +1,12 @@ +on: + pull_request: + types: [opened, synchronize] + +name: Generate Pull Request Stats + +jobs: + stats: + name: PR Stats + runs-on: ubuntu-latest + steps: + - uses: zeit/next-stats-action@master diff --git a/.github/workflows/release_stats.yml b/.github/workflows/release_stats.yml new file mode 100644 index 00000000000000..20cc819388033e --- /dev/null +++ b/.github/workflows/release_stats.yml @@ -0,0 +1,12 @@ +on: release + +name: Generate Release Stats + +jobs: + prStats: + name: Release Stats + runs-on: ubuntu-latest + steps: + - uses: zeit/next-stats-action@master + env: + PR_STATS_COMMENT_TOKEN: ${{ secrets.PR_STATS_COMMENT_TOKEN }} diff --git a/.github/workflows/test_react_next.yml b/.github/workflows/test_react_next.yml new file mode 100644 index 00000000000000..443b0e25af5184 --- /dev/null +++ b/.github/workflows/test_react_next.yml @@ -0,0 +1,53 @@ +on: + schedule: + # * is a special character in YAML so you have to quote this string + - cron: '0 0,12 * * *' + +name: Test react@next + +jobs: + # build: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v2 + + # - run: yarn install --frozen-lockfile --check-files + # env: + # NEXT_TELEMETRY_DISABLED: 1 + + # - run: yarn upgrade react@next react-dom@next -W --dev + + # - uses: actions/cache@v1 + # id: cache-build + # with: + # path: '.' + # key: ${{ github.sha }} + + testAll: + name: Test All + runs-on: ubuntu-latest + # needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + HEADLESS: true + strategy: + fail-fast: false + matrix: + group: [1, 2, 3, 4, 5, 6] + steps: + # - uses: actions/cache@v1 + # id: restore-build + # with: + # path: '.' + # key: ${{ github.sha }} + + - uses: actions/checkout@v2 + + - run: yarn install --frozen-lockfile --check-files + + - run: yarn upgrade react@next react-dom@next -W --dev + + # TODO: remove after we fix watchpack watching too much + - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + + - run: node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 diff --git a/.gitignore b/.gitignore index 86ecc6d4c46ba9..774d9ab5fa0a69 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ coverage # test output test/**/out* +test/**/next-env.d.ts .DS_Store # Editors diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000000000..3701997c2bbfcc --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +node_modules +**/.next/** +**/_next/** +**/dist/** \ No newline at end of file diff --git a/.prettierignore_staged b/.prettierignore_staged new file mode 100644 index 00000000000000..7278db6b0605e4 --- /dev/null +++ b/.prettierignore_staged @@ -0,0 +1,3 @@ +**/.next/** +**/_next/** +**/dist/** \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000000000..0b4951088a6707 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "semi": false, + "trailingComma": "es5" +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 077e500454d89b..00000000000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -{ - sudo: "required", - dist: "trusty", - addons: { - apt: { - sources: ["google-chrome"], - packages: ["google-chrome-stable"] - } - }, - env: { - HEADLESS: 'false' - }, - language: "node_js", - node_js: ["8", "10"], - cache: { - directories: ["node_modules"] - }, - before_install: [ - "curl -o- -L https://yarnpkg.com/install.sh | bash", - "export PATH=\"$HOME/.yarn/bin:$PATH\"", - "export DISPLAY=:99.0", - "sh -e /etc/init.d/xvfb start", - "sleep 3" - ], - before_cache: [ - "rm -rf node_modules/.cache" - ], - before_script: [], - after_script: ["yarn run coveralls"] -} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000000..e521849f2cdba2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "eslint.validate": [ + "javascript", + "javascriptreact", + { "language": "typescript", "autoFix": true }, + { "language": "typescriptreact", "autoFix": true } + ] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000000..f264ef949da557 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [abuse@zeit.co](mailto:abuse@zeit.co). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/README-zh-CN.md b/README-zh-CN.md deleted file mode 100644 index d617f60e8cb13b..00000000000000 --- a/README-zh-CN.md +++ /dev/null @@ -1,2102 +0,0 @@ -screen shot 2016-10-25 at 2 37 27 pm - -[![NPM version](https://img.shields.io/npm/v/next.svg)](https://www.npmjs.com/package/next) -[![Build Status](https://travis-ci.org/zeit/next.js.svg?branch=master)](https://travis-ci.org/zeit/next.js) -[![Build status](https://ci.appveyor.com/api/projects/status/gqp5hs71l3ebtx1r/branch/master?svg=true)](https://ci.appveyor.com/project/arunoda/next-js/branch/master) -[![Coverage Status](https://coveralls.io/repos/zeit/next.js/badge.svg?branch=master)](https://coveralls.io/r/zeit/next.js?branch=master) -[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/next-js) - -Next.js 是一个轻量级的 React 服务端渲染应用框架。 - -**可访问 [nextjs.org/learn](https://nextjs.org/learn) 开始学习 Next.js.** - -[README in English](README.md) - ---- - - - - - -- [怎么使用](#how-to-use) - - [安装](#setup) - - [代码自动分割](#automatic-code-splitting) - - [CSS](#css) - - [支持嵌入样式](#built-in-css-support) - - [内嵌样式](#css-in-js) - - [使用 CSS / Sass / Less / Stylus files](#importing-css--sass--less--stylus-files) - - [静态文件服务(如图像)](#static-file-serving-eg-images) - - [``](#populating-head) - - [获取数据以及组件生命周期](#fetching-data-and-component-lifecycle) - - [路由](#routing) - - [`` 用法](#with-link) - - [URL 对象](#with-url-object) - - [替换路由](#replace-instead-of-push-url) - - [组件支持点击事件`onClick`](#using-a-component-that-supports-onclick) - - [暴露`href`给子元素](#forcing-the-link-to-expose-href-to-its-child) - - [禁止滚动到页面顶部](#disabling-the-scroll-changes-to-top-on-page) - - [命令式](#imperatively) - - [拦截器 `popstate`](#intercepting-popstate) - - [URL 对象用法](#with-url-object-1) - - [路由事件](#router-events) - - [浅层路由](#shallow-routing) - - [高阶组件](#using-a-higher-order-component) - - [预加载页面](#prefetching-pages) - - [``用法](#with-link-1) - - [命令式 prefetch 写法](#imperatively-1) - - [自定义服务端路由](#custom-server-and-routing) - - [禁止文件路由](#disabling-file-system-routing) - - [动态前缀](#dynamic-assetprefix) - - [动态导入](#dynamic-import) - - [1. 基础支持 (同样支持 SSR)](#1-basic-usage-also-does-ssr) - - [2. 自定义加载组件](#2-with-custom-loading-component) - - [3. 禁止使用 SSR](#3-with-no-ssr) - - [4. 同时加载多个模块](#4-with-multiple-modules-at-once) - - [自定义 ``](#custom-app) - - [自定义 ``](#custom-document) - - [自定义错误处理](#custom-error-handling) - - [渲染内置错误页面](#reusing-the-built-in-error-page) - - [自定义配置](#custom-configuration) - - [设置自定义构建目录](#setting-a-custom-build-directory) - - [禁止 etag 生成](#disabling-etag-generation) - - [配置 onDemandEntries](#configuring-the-ondemandentries) - - [配置页面后缀名解析扩展](#configuring-extensions-looked-for-when-resolving-pages-in-pages) - - [配置构建 ID](#configuring-the-build-id) - - [自定义 webpack 配置](#customizing-webpack-config) - - [自定义 babel 配置](#customizing-babel-config) - - [暴露配置到服务端和客户端](#exposing-configuration-to-the-server--client-side) - - [启动服务选择 hostname](#starting-the-server-on-alternative-hostname) - - [CDN 支持前缀](#cdn-support-with-asset-prefix) -- [项目部署](#production-deployment) -- [浏览器支持](#browser-support) -- [导出静态页面](#static-html-export) - - [使用](#usage) - - [限制](#limitation) -- [多 zone](#multi-zones) - - [怎么定义一个 zone](#how-to-define-a-zone) - - [怎么合并他们](#how-to-merge-them) -- [技巧](#recipes) -- [FAQ](#faq) -- [贡献](#contributing) -- [作者](#authors) - - - - - -## 怎么使用 - - - -### 安装 - -在项目文件夹中运行: - -```bash -npm install --save next react react-dom -``` - -将下面脚本添加到 package.json 中: - -```json -{ - "scripts": { - "dev": "next", - "build": "next build", - "start": "next start" - } -} -``` - -下面, 文件系统是主要的 API. 每个`.js` 文件将变成一个路由,自动处理和渲染。 - -新建 `./pages/index.js` 到你的项目中: - -```jsx -export default () =>
Welcome to next.js!
; -``` - -运行 `npm run dev` 命令并打开 `http://localhost:3000`。 要使用其他端口,你可以运行 `npm run dev -- -p `. - -到目前为止,我们做到: - -- 自动打包编译 (使用 webpack 和 babel) -- 热加载 -- 以 `./pages`作为服务的渲染和索引 -- 静态文件服务. `./static/` 映射到 `/static/` (可以 [创建一个静态目录](#static-file-serving-eg-images) 在你的项目中) - -这里有个简单的案例,可以下载看看 [sample app - nextgram](https://github.com/zeit/nextgram) - - - -### 代码自动分割 - -每个页面只会导入`import`中绑定以及被用到的代码. 这意味着页面不会加载不必要的代码 - -```jsx -import cowsay from "cowsay-browser"; - -export default () =>
{cowsay.say({ text: "hi there!" })}
; -``` - - - -### CSS - - - -#### 支持嵌入样式 - -

- 案例 - -

- -我们绑定 [styled-jsx](https://github.com/zeit/styled-jsx) 来生成独立作用域的 CSS. 目标是支持 "shadow CSS",但是 [不支持独立模块作用域的 JS](https://github.com/w3c/webcomponents/issues/71). - -```jsx -export default () => ( -
- Hello world -

scoped!

- - -
-); -``` - -想查看更多案例可以点击 [styled-jsx documentation](https://www.npmjs.com/package/styled-jsx). - - - -#### 内嵌样式 - -

- - Examples - - -

- -有些情况可以使用 CSS 内嵌 JS 写法。如下所示: - -```jsx -export default () =>

hi there

; -``` - -更复杂的内嵌样式解决方案,特别是服务端渲染时的样式更改。我们可以通过包裹自定义 Document,来添加样式,案例如下:[custom ``](#user-content-custom-document) - - - -#### 使用 CSS / Sass / Less / Stylus files - -支持用`.css`, `.scss`, `.less` or `.styl`,需要配置默认文件 next.config.js,具体可查看下面链接 - -- [@zeit/next-css](https://github.com/zeit/next-plugins/tree/master/packages/next-css) -- [@zeit/next-sass](https://github.com/zeit/next-plugins/tree/master/packages/next-sass) -- [@zeit/next-less](https://github.com/zeit/next-plugins/tree/master/packages/next-less) -- [@zeit/next-stylus](https://github.com/zeit/next-plugins/tree/master/packages/next-stylus) - - - -### 静态文件服务(如图像) - -在根目录下新建文件夹叫`static`。代码可以通过`/static/`来引入相关的静态资源。 - -```jsx -export default () => my image; -``` - -_注意:不要自定义静态文件夹的名字,只能叫`static` ,因为只有这个名字 Next.js 才会把它当作静态资源。_ - - - -### 生成`` - -`` - -

- Examples - -

- -我们设置一个内置组件来装载``到页面中。 - -```jsx -import Head from "next/head"; - -export default () => ( -
- - My page title - - -

Hello world!

-
-); -``` - -我们定义`key`属性来避免重复的``标签,保证``只渲染一次,如下所示: - -```jsx -import Head from "next/head"; -export default () => ( -
- - My page title - - - - - -

Hello world!

-
-); -``` - -只有第二个``才被渲染。 - -_注意:在卸载组件时,``的内容将被清除。请确保每个页面都在其``定义了所需要的内容,而不是假设其他页面已经加过了_ - - - -### 获取数据以及组件生命周期 - -

- Examples - -

- -当你需要状态,生命周期钩子或初始数据填充时,你可以导出`React.Component`(而不是上面的无状态函数),如下所示: - -```jsx -import React from "react"; - -export default class extends React.Component { - static async getInitialProps({ req }) { - const userAgent = req ? req.headers["user-agent"] : navigator.userAgent; - return { userAgent }; - } - - render() { - return
Hello World {this.props.userAgent}
; - } -} -``` - -请注意,当页面渲染时加载数据,我们使用了一个异步静态方法`getInitialProps`。它能异步获取 JS 普通对象,并绑定在`props`上。 - -当服务渲染时,`getInitialProps`将会把数据序列化,就像`JSON.stringify`。所以确保`getInitialProps`返回的是一个普通 JS 对象,而不是`Date`, `Map` 或 `Set`类型。 - -当页面初次加载时,`getInitialProps`只会在服务端执行一次。`getInitialProps`只有在路由切换的时候(如`Link`组件跳转或路由自定义跳转)时,客户端的才会被执行。 - -当页面初始化加载时,`getInitialProps`仅在服务端上执行。只有当路由跳转(`Link`组件跳转或 API 方法跳转)时,客户端才会执行`getInitialProps`。 - -注意:`getInitialProps`将不能在子组件中使用。只能在`pages`页面中使用。 - -
- -> 只有服务端用到的模块放在`getInitialProps`里,请确保正确的导入了它们,可参考[import them properly](https://arunoda.me/blog/ssr-and-server-only-modules)。 -> 否则会拖慢你的应用速度。 - -
- -你也可以给无状态组件定义`getInitialProps`: - -```jsx -const Page = ({ stars }) =>
Next stars: {stars}
; - -Page.getInitialProps = async ({ req }) => { - const res = await fetch("https://api.github.com/repos/zeit/next.js"); - const json = await res.json(); - return { stars: json.stargazers_count }; -}; - -export default Page; -``` - -`getInitialProps`入参对象的属性如下: - -- `pathname` - URL 的 path 部分 -- `query` - URL 的 query 部分,并被解析成对象 -- `asPath` - 显示在浏览器中的实际路径(包含查询部分),为`String`类型 -- `req` - HTTP 请求对象 (仅限服务器端) -- `res` - HTTP 返回对象 (仅限服务器端) -- `jsonPageRes` - 获取响应对象(仅限客户端) -- `err` - 渲染过程中的任何错误 - - - -### 路由 - -Next.js不会随应用程序中每个可能的路由一起发布路由清单,因此当前页面不知道客户端上的任何其他页面。出于可扩展性考虑,所有后续路由都会惰性加载。 - - - -#### ``用法 - -

- Examples - -

- -可以用 `` 组件实现客户端的路由切换。 - -**基本例子** - -参考下面的两个页面: - -```jsx -// pages/index.js -import Link from 'next/link' - -function Home() { - return ( -
- Click{' '} - - here - {' '} - to read more -
- ) -} - -export default Home -``` - -```jsx -// pages/about.js -function About() { - return

Welcome to About!

-} - -export default About -``` - -**自定义路由 (使用URL中的props)** - -`` 组件有两个主要属性: - -- `href`: `pages`目录内的路径+查询字符串. -- `as`: 将在浏览器URL栏中呈现的路径. - -例子: - -1. 假设你有个这样的路由 `/post/:slug`. - -2. 你可以创建文件 `pages/post.js` - -```jsx -class Post extends React.Component { - static async getInitialProps({ query }) { - console.log('SLUG', query.slug) - return {} - } - render() { - return

My blog post

- } -} - -export default Post -``` - -3. 将路由添加到 `express` (或者其他服务端) 的 `server.js` 文件 (这仅适用于SSR). 这将解析`/post/:slug`到`pages/post.js`并在getInitialProps中提供`slug`作为查询的一部分。 - -```jsx -server.get('/post/:slug', (req, res) => { - return app.render(req, res, '/post', { slug: req.params.slug }) -}) -``` - -4. 对于客户端路由,使用 `next/link`: -```jsx - -``` - -_注意:可以使用[``](#prefetching-pages)使链接和预加载在后台同时进行,来达到页面的最佳性能。_ - -客户端路由行为与浏览器很相似: - -1. 获取组件 -2. 如果组件定义了`getInitialProps`,则获取数据。如果有错误情况将会渲染 `_error.js`。 -3. 1 和 2 都完成了,`pushState`执行,新组件被渲染。 - -如果需要注入`pathname`, `query` 或 `asPath`到你组件中,你可以使用[withRouter](#using-a-higher-order-component)。 - - - -##### URL 对象 - -

- Examples - -

- -组件``接收 URL 对象,而且它会自动格式化生成 URL 字符串 - -```jsx -// pages/index.js -import Link from "next/link"; - -export default () => ( -
- Click{" "} - - here - {" "} - to read more -
-); -``` - -将生成 URL 字符串`/about?name=Zeit`,你可以使用任何在[Node.js URL module documentation](https://nodejs.org/api/url.html#url_url_strings_and_url_objects)定义过的属性。 - - - -##### 替换路由 - -``组件默认将新 url 推入路由栈中。你可以使用`replace`属性来防止添加新输入。 - -```jsx -// pages/index.js -import Link from "next/link"; - -export default () => ( -
- Click{" "} - - here - {" "} - to read more -
-); -``` - - - -##### 组件支持点击事件 `onClick` - -``支持每个组件所支持的`onClick`事件。如果你不提供``标签,只会处理`onClick`事件而`href`将不起作用。 - -```jsx -// pages/index.js -import Link from "next/link"; - -export default () => ( -
- Click{" "} - - image - -
-); -``` - -
- -##### 暴露 `href` 给子元素 - -如子元素是一个没有 href 属性的``标签,我们将会指定它以免用户重复操作。然而有些时候,我们需要里面有``标签,但是`Link`组件不会被识别成*超链接*,结果不能将`href`传递给子元素。在这种场景下,你可以定义一个`Link`组件中的布尔属性`passHref`,强制将`href`传递给子元素。 - -**注意**: 使用`a`之外的标签而且没有通过`passHref`的链接可能会使导航看上去正确,但是当搜索引擎爬行检测时,将不会识别成链接(由于缺乏 href 属性),这会对你网站的 SEO 产生负面影响。 - -```jsx -import Link from "next/link"; -import Unexpected_A from "third-library"; - -export default ({ href, name }) => ( - - {name} - -); -``` - - - -##### 禁止滚动到页面顶部 - -``的默认行为就是滚到页面顶部。当有 hash 定义时(#),页面将会滚动到对应的 id 上,就像``标签一样。为了预防滚动到顶部,可以给``加 -`scroll={false}`属性: - -```jsx -Disables scrolling -Changes with scrolling to top -``` - - - -#### 命令式 - -

- Examples - -

- -你也可以用`next/router`实现客户端路由切换 - -```jsx -import Router from "next/router"; - -export default () => ( -
- Click Router.push("/about")}>here to read more -
-); -``` - - - -#### 拦截器 `popstate` - -有些情况(比如使用[custom router](#custom-server-and-routing)),你可能想监听[`popstate`](https://developer.mozilla.org/en-US/docs/Web/Events/popstate),在路由跳转前做一些动作。 -比如,你可以操作 request 或强制 SSR 刷新 - -```jsx -import Router from "next/router"; - -Router.beforePopState(({ url, as, options }) => { - // I only want to allow these two routes! - if (as !== "/" || as !== "/other") { - // Have SSR render bad routes as a 404. - window.location.href = as; - return false; - } - - return true; -}); -``` - -如果你在`beforePopState`中返回 false,`Router`将不会执行`popstate`事件。 -例如[Disabling File-System Routing](#disabling-file-system-routing)。 - -以上`Router`对象的 API 如下: - -- `route` - 当前路由,为`String`类型 -- `pathname` - 不包含查询内容的当前路径,为`String`类型 -- `query` - 查询内容,被解析成`Object`类型. 默认为`{}` -- `asPath` - 展现在浏览器上的实际路径,包含查询内容,为`String`类型 -- `push(url, as=url)` - 用给定的url调用`pushState` -- `replace(url, as=url)` - 用给定的url调用`replaceState` -- `beforePopState(cb=function)` - 在路由器处理事件之前拦截. - -`push` 和 `replace` 函数的第二个参数`as`,是为了装饰 URL 作用。如果你在服务器端设置了自定义路由将会起作用。 - - - -##### URL 对象用法 - -`push` 或 `replace`可接收的 URL 对象(``组件的 URL 对象一样)来生成 URL。 - -```jsx -import Router from "next/router"; - -const handler = () => - Router.push({ - pathname: "/about", - query: { name: "Zeit" } - }); - -export default () => ( -
- Click here to read more -
-); -``` - -也可以像``组件一样添加额外的参数。 - - - -##### 路由事件 - -你可以监听路由相关事件。 -下面是支持的事件列表: - -- `routeChangeStart(url)` - 路由开始切换时触发 -- `routeChangeComplete(url)` - 完成路由切换时触发 -- `routeChangeError(err, url)` - 路由切换报错时触发 -- `beforeHistoryChange(url)` - 浏览器 history 模式开始切换时触发 -- `hashChangeStart(url)` - 开始切换 hash 值但是没有切换页面路由时触发 -- `hashChangeComplete(url)` - 完成切换 hash 值但是没有切换页面路由时触发 - -> 这里的`url`是指显示在浏览器中的 url。如果你用了`Router.push(url, as)`(或类似的方法),那浏览器中的 url 将会显示 as 的值。 - -下面是如何正确使用路由事件`routeChangeStart`的例子: - -```js -const handleRouteChange = url => { - console.log("App is changing to: ", url); -}; - -Router.events.on("routeChangeStart", handleRouteChange); -``` - -如果你不想再监听该事件,你可以用`off`事件去取消监听: - -```js -Router.events.off("routeChangeStart", handleRouteChange); -``` - -如果路由加载被取消(比如快速连续双击链接),`routeChangeError`将触发。传递err,并且属性cancelled的值为true。 - -```js -Router.events.on("routeChangeError", (err, url) => { - if (err.cancelled) { - console.log(`Route to ${url} was cancelled!`); - } -}); -``` - - - -##### 浅层路由 - -

- Examples - -

- -浅层路由允许你改变 URL 但是不执行`getInitialProps`生命周期。你可以加载相同页面的 URL,得到更新后的路由属性`pathname`和`query`,并不失去 state 状态。 - -你可以给`Router.push` 或 `Router.replace`方法加`shallow: true`参数。如下面的例子所示: - -```js -// Current URL is "/" -const href = "/?counter=10"; -const as = href; -Router.push(href, as, { shallow: true }); -``` - -现在 URL 更新为`/?counter=10`。在组件里查看`this.props.router.query`你将会看到更新的 URL。 - -你可以在[`componentdidupdate`](https://facebook.github.io/react/docs/react-component.html#componentdidupdate)钩子函数中监听 URL 的变化。 - -```js -componentDidUpdate(prevProps) { - const { pathname, query } = this.props.router - // verify props have changed to avoid an infinite loop - if (query.id !== prevProps.router.query.id) { - // fetch data based on the new query - } -} -``` - -> 注意: -> -> 浅层路由只作用于相同 URL 的参数改变,比如我们假定有个其他路由`about`,而你向下面代码样运行: -> -> ```js -> Router.push("/?counter=10", "/about?counter=10", { shallow: true }); -> ``` -> -> 那么这将会出现新页面,即使我们加了浅层路由,但是它还是会卸载当前页,会加载新的页面并触发新页面的`getInitialProps`。 - - - -#### 高阶组件 - -

- Examples - -

- -如果你想应用里每个组件都处理路由对象,你可以使用`withRouter`高阶组件。下面是如何使用它: - -```jsx -import { withRouter } from "next/router"; - -const ActiveLink = ({ children, router, href }) => { - const style = { - marginRight: 10, - color: router.pathname === href ? "red" : "black" - }; - - const handleClick = e => { - e.preventDefault(); - router.push(href); - }; - - return ( - - {children} - - ); -}; - -export default withRouter(ActiveLink); -``` - -上面路由对象的 API 可以参考[`next/router`](#imperatively). - - - -### 预加载页面 - -⚠️ 只有生产环境才有此功能 ⚠️ - -

- Examples - -

- -Next.js 有允许你预加载页面的 API。 - -用 Next.js 服务端渲染你的页面,可以达到所有你应用里所有未来会跳转的路径即时响应,有效的应用 Next.js,可以通过预加载应用程序的功能,最大程度的初始化网站性能。[查看更多](https://zeit.co/blog/next#anticipation-is-the-key-to-performance). - -> Next.js 的预加载功能只预加载 JS 代码。当页面渲染时,你可能需要等待数据请求。 - - - -#### ``用法 - -你可以给添加 `prefetch` 属性,Next.js 将会在后台预加载这些页面。 - -```jsx -import Link from "next/link"; - -// example header component -export default () => ( - -); -``` - - - -#### 命令式 prefetch 写法 - -大多数预加载是通过处理的,但是我们还提供了命令式 API 用于更复杂的场景。 - -```jsx -import { withRouter } from "next/router"; - -export default withRouter(({ router }) => ( -
- setTimeout(() => router.push("/dynamic"), 100)}> - A route transition will happen after 100ms - - {// but we can prefetch it! - router.prefetch("/dynamic")} -
-)); -``` - -路由实例只允许在应用程序的客户端。以防服务端渲染发生错误,建议 prefetch 事件写在`componentDidMount()`生命周期里。 - -```jsx -import React from "react"; -import { withRouter } from "next/router"; - -class MyLink extends React.Component { - componentDidMount() { - const { router } = this.props; - router.prefetch("/dynamic"); - } - - render() { - const { router } = this.props; - return ( -
- setTimeout(() => router.push("/dynamic"), 100)}> - A route transition will happen after 100ms - -
- ); - } -} - -export default withRouter(MyLink); -``` - - - -### 自定义服务端路由 - -

- Examples - -

- -一般你使用`next start`命令来启动 next 服务,你还可以编写代码来自定义路由,如使用路由正则等。 - -当使用自定义服务文件,如下面例子所示叫 server.js 时,确保你更新了 package.json 中的脚本。 - -```json -{ - "scripts": { - "dev": "node server.js", - "build": "next build", - "start": "NODE_ENV=production node server.js" - } -} -``` - -下面这个例子使 `/a` 路由解析为`./pages/b`,以及`/b` 路由解析为`./pages/a`; - -```js -// This file doesn't go through babel or webpack transformation. -// Make sure the syntax and sources this file requires are compatible with the current node version you are running -// See https://github.com/zeit/next.js/issues/1245 for discussions on Universal Webpack or universal Babel -const { createServer } = require("http"); -const { parse } = require("url"); -const next = require("next"); - -const dev = process.env.NODE_ENV !== "production"; -const app = next({ dev }); -const handle = app.getRequestHandler(); - -app.prepare().then(() => { - createServer((req, res) => { - // Be sure to pass `true` as the second argument to `url.parse`. - // This tells it to parse the query portion of the URL. - const parsedUrl = parse(req.url, true); - const { pathname, query } = parsedUrl; - - if (pathname === "/a") { - app.render(req, res, "/b", query); - } else if (pathname === "/b") { - app.render(req, res, "/a", query); - } else { - handle(req, res, parsedUrl); - } - }).listen(3000, err => { - if (err) throw err; - console.log("> Ready on http://localhost:3000"); - }); -}); -``` - -`next`的 API 如下所示 - -- `next(opts: object)` - -opts 的属性如下: - -- `dev` (`boolean`) 判断 Next.js 应用是否在开发环境 - 默认`false` -- `dir` (`string`) Next 项目路径 - 默认`'.'` -- `quiet` (`boolean`) 是否隐藏包含服务端消息在内的错误信息 - 默认`false` -- `conf` (`object`) 与`next.config.js`的对象相同 - 默认`{}` - -生产环境的话,可以更改 package.json 里的`start`脚本为`NODE_ENV=production node server.js`。 - - - -#### 禁止文件路由 - -默认情况,`Next`将会把`/pages`下的所有文件匹配路由(如`/pages/some-file.js` 渲染为 `site.com/some-file`) - -如果你的项目使用自定义路由,那么有可能不同的路由会得到相同的内容,可以优化 SEO 和用户体验。 - -禁止路由链接到`/pages`下的文件,只需设置`next.config.js`文件如下所示: - -```js -// next.config.js -module.exports = { - useFileSystemPublicRoutes: false -}; -``` - -注意`useFileSystemPublicRoutes`只禁止服务端的文件路由;但是客户端的还是禁止不了。 - -你如果想配置客户端路由不能跳转文件路由,可以参考[Intercepting `popstate`](#intercepting-popstate)。 - - - -#### 动态前缀 - -有时你需要设置动态前缀,可以在请求时设置`assetPrefix`改变前缀。 - -使用方法如下: - -```js -const next = require("next"); -const micro = require("micro"); - -const dev = process.env.NODE_ENV !== "production"; -const app = next({ dev }); -const handleNextRequests = app.getRequestHandler(); - -app.prepare().then(() => { - const server = micro((req, res) => { - // Add assetPrefix support based on the hostname - if (req.headers.host === "my-app.com") { - app.setAssetPrefix("http://cdn.com/myapp"); - } else { - app.setAssetPrefix(""); - } - - handleNextRequests(req, res); - }); - - server.listen(port, err => { - if (err) { - throw err; - } - - console.log(`> Ready on http://localhost:${port}`); - }); -}); -``` - - - -### 动态导入 - -

- Examples - -

- -ext.js 支持 JavaScript 的 TC39 提议[dynamic import proposal](https://github.com/tc39/proposal-dynamic-import)。你可以动态导入 JavaScript 模块(如 React 组件)。 - -动态导入相当于把代码分成各个块管理。Next.js 服务端动态导入功能,你可以做很多炫酷事情。 - -下面介绍一些动态导入方式: - - - -#### 1. 基础用法 (也就是SSR) - -```jsx -import dynamic from "next/dynamic"; - -const DynamicComponent = dynamic(import("../components/hello")); - -export default () => ( -
-
- -

HOME PAGE is here!

-
-); -``` - - - -#### 2. 自定义加载组件 - -```jsx -import dynamic from "next/dynamic"; - -const DynamicComponentWithCustomLoading = dynamic( - import("../components/hello2"), - { - loading: () =>

...

- } -); - -export default () => ( -
-
- -

HOME PAGE is here!

-
-); -``` - - - -#### 3. 禁止使用 SSR - -```jsx -import dynamic from "next/dynamic"; - -const DynamicComponentWithNoSSR = dynamic(import("../components/hello3"), { - ssr: false -}); - -export default () => ( -
-
- -

HOME PAGE is here!

-
-); -``` - - - -#### 4. 同时加载多个模块 - -```jsx -import dynamic from "next/dynamic"; - -const HelloBundle = dynamic({ - modules: () => { - const components = { - Hello1: import("../components/hello1"), - Hello2: import("../components/hello2") - }; - - return components; - }, - render: (props, { Hello1, Hello2 }) => ( -
-

{props.title}

- - -
- ) -}); - -export default () => ; -``` - - - -### 自定义 `` - -

- Examples - - -

- -组件来初始化页面。你可以重写它来控制页面初始化,如下面的事: - -- 当页面变化时保持页面布局 -- 当路由变化时保持页面状态 -- 使用`componentDidCatch`自定义处理错误 -- 注入额外数据到页面里 (如 GraphQL 查询) - -重写的话,新建`./pages/_app.js`文件,重写 App 模块如下所示: - -```js -import App, { Container } from "next/app"; -import React from "react"; - -export default class MyApp extends App { - static async getInitialProps({ Component, router, ctx }) { - let pageProps = {}; - - if (Component.getInitialProps) { - pageProps = await Component.getInitialProps(ctx); - } - - return { pageProps }; - } - - render() { - const { Component, pageProps } = this.props; - return ( - - - - ); - } -} -``` - - - -### 自定义 `` - -

- Examples - - -

- -- 在服务端呈现 -- 初始化服务端时添加文档标记元素 -- 通常实现服务端渲染会使用一些 css-in-js 库,如[styled-components](./examples/with-styled-components), [glamorous](./examples/with-glamorous) 或 [emotion](with-emotion)。[styled-jsx](https://github.com/zeit/styled-jsx)是 Next.js 自带默认使用的 css-in-js 库 - -`Next.js`会自动定义文档标记,比如,你从来不需要添加``, ``等。如果想自定义文档标记,你可以新建`./pages/_document.js`,然后扩展`Document`类: - -```jsx -// _document is only rendered on the server side and not on the client side -// Event handlers like onClick can't be added to this file - -// ./pages/_document.js -import Document, { Head, Main, NextScript } from "next/document"; - -export default class MyDocument extends Document { - static async getInitialProps(ctx) { - const initialProps = await Document.getInitialProps(ctx); - return { ...initialProps }; - } - - render() { - return ( - - - - - -
- - - - ); - } -} -``` - -钩子[`getInitialProps`](#fetching-data-and-component-lifecycle)接收到的参数`ctx`对象都是一样的 - -- 回调函数`renderPage`是会执行 React 渲染逻辑的函数(同步),这种做法有助于此函数支持一些类似于 Aphrodite 的 renderStatic 等一些服务器端渲染容器。 - -**注意:`
`外的 React 组件将不会渲染到浏览器中,所以那添加应用逻辑代码。如果你页面需要公共组件(菜单或工具栏),可以参照上面说的`App`组件代替。** - - - -#### 自定义 `renderPage` - -🚧 应该注意的是,您应该定制“renderPage”的唯一原因是使用css-in-js库,需要将应用程序包装起来以正确使用服务端渲染。 🚧 - -- 它将一个选项对象作为参数进行进一步的自定义: - -```js -import Document from 'next/document' - -class MyDocument extends Document { - static async getInitialProps(ctx) { - const originalRenderPage = ctx.renderPage - - ctx.renderPage = () => - originalRenderPage({ - // useful for wrapping the whole react tree - enhanceApp: App => App, - // useful for wrapping in a per-page basis - enhanceComponent: Component => Component - }) - - // Run the parent `getInitialProps` using `ctx` that now includes our custom `renderPage` - const initialProps = await Document.getInitialProps(ctx) - - return initialProps - } -} - -export default MyDocument -``` - - -### 自定义错误处理 - -404 和 500 错误客户端和服务端都会通过`error.js`组件处理。如果你想改写它,则新建`_error.js`在文件夹中: - -⚠️ 该`pages/_error.js`组件仅用于生产。在开发过程中,您会收到调用堆栈错误,以了解错误源自何处。 ⚠️ - -```jsx -import React from "react"; - -export default class Error extends React.Component { - static getInitialProps({ res, err }) { - const statusCode = res ? res.statusCode : err ? err.statusCode : null; - return { statusCode }; - } - - render() { - return ( -

- {this.props.statusCode - ? `An error ${this.props.statusCode} occurred on server` - : "An error occurred on client"} -

- ); - } -} -``` - - - -### 渲染内置错误页面 - -如果你想渲染内置错误页面,你可以使用`next/error`: - -```jsx -import React from "react"; -import Error from "next/error"; -import fetch from "isomorphic-unfetch"; - -export default class Page extends React.Component { - static async getInitialProps() { - const res = await fetch("https://api.github.com/repos/zeit/next.js"); - const statusCode = res.statusCode > 200 ? res.statusCode : false; - const json = await res.json(); - - return { statusCode, stars: json.stargazers_count }; - } - - render() { - if (this.props.statusCode) { - return ; - } - - return
Next stars: {this.props.stars}
; - } -} -``` - -> 如果你自定义了个错误页面,你可以引入自己的错误页面来代替`next/error` - - - -### 自定义配置 - -如果你想自定义 Next.js 的高级配置,可以在根目录下新建`next.config.js`文件(与`pages/` 和 `package.json`一起) - -注意:`next.config.js`是一个 Node.js 模块,不是一个 JSON 文件,可以用于 Next 启动服务已经构建阶段,但是不作用于浏览器端。 - -```js -// next.config.js -module.exports = { - /* config options here */ -}; -``` - -或使用一个函数: - -```js -module.exports = (phase, { defaultConfig }) => { - // - // https://github.com/zeit/ - return { - /* config options here */ - }; -}; -``` - -`phase`是配置文件被加载时的当前内容。你可看到所有的 phases 常量:[constants](./lib/constants.js) -这些常量可以通过`next/constants`引入: - -```js -const { PHASE_DEVELOPMENT_SERVER } = require("next/constants"); -module.exports = (phase, { defaultConfig }) => { - if (phase === PHASE_DEVELOPMENT_SERVER) { - return { - /* development only config options here */ - }; - } - - return { - /* config options for all phases except development here */ - }; -}; -``` - - - -#### 设置自定义构建目录 - -你可以自定义一个构建目录,如新建`build`文件夹来代替`.next` 文件夹成为构建目录。如果没有配置构建目录,构建时将会自动新建`.next`文件夹 - -```js -// next.config.js -module.exports = { - distDir: "build" -}; -``` - - - -#### 禁止 etag 生成 - -你可以禁止 etag 生成根据你的缓存策略。如果没有配置,Next 将会生成 etags 到每个页面中。 - -```js -// next.config.js -module.exports = { - generateEtags: false -}; -``` - - - -#### 配置 onDemandEntries - -Next 暴露一些选项来给你控制服务器部署以及缓存页面: - -```js -module.exports = { - onDemandEntries: { - // period (in ms) where the server will keep pages in the buffer - maxInactiveAge: 25 * 1000, - // number of pages that should be kept simultaneously without being disposed - pagesBufferLength: 2 - } -}; -``` - -这个只是在开发环境才有的功能。如果你在生成环境中想缓存 SSR 页面,请查看[SSR-caching](https://github.com/zeit/next.js/tree/canary/examples/ssr-caching) - - - -#### 配置解析路由时的页面文件后缀名 - -如 typescript 模块[`@zeit/next-typescript`](https://github.com/zeit/next-plugins/tree/master/packages/next-typescript),需要支持解析后缀名为`.ts`的文件。`pageExtensions` 允许你扩展后缀名来解析各种 pages 下的文件。 - -```js -// next.config.js -module.exports = { - pageExtensions: ["jsx", "js"] -}; -``` - - - -#### 配置构建 ID - -Next.js 使用构建时生成的常量来标识你的应用服务是哪个版本。在每台服务器上运行构建命令时,可能会导致多服务器部署出现问题。为了保持同一个构建 ID,可以配置`generateBuildId`函数: - -```js -// next.config.js -module.exports = { - generateBuildId: async () => { - // For example get the latest git commit hash here - return "my-build-id"; - } -}; -``` - - - -### 自定义 webpack 配置 - -

- Examples - -

- -可以使用些一些常见的模块 - -- [@zeit/next-css](https://github.com/zeit/next-plugins/tree/master/packages/next-css) -- [@zeit/next-sass](https://github.com/zeit/next-plugins/tree/master/packages/next-sass) -- [@zeit/next-less](https://github.com/zeit/next-plugins/tree/master/packages/next-less) -- [@zeit/next-preact](https://github.com/zeit/next-plugins/tree/master/packages/next-preact) -- [@zeit/next-typescript](https://github.com/zeit/next-plugins/tree/master/packages/next-typescript) - -_注意: `webpack`方法将被执行两次,一次在服务端一次在客户端。你可以用`isServer`属性区分客户端和服务端来配置_ - -多配置可以组合在一起,如: - -```js -const withTypescript = require("@zeit/next-typescript"); -const withSass = require("@zeit/next-sass"); - -module.exports = withTypescript( - withSass({ - webpack(config, options) { - // Further custom configuration here - return config; - } - }) -); -``` - -为了扩展`webpack`使用,可以在`next.config.js`定义函数。 - -```js -// next.config.js is not transformed by Babel. So you can only use javascript features supported by your version of Node.js. - -module.exports = { - webpack: (config, { buildId, dev, isServer, defaultLoaders }) => { - // Perform customizations to webpack config - // Important: return the modified config - return config; - }, - webpackDevMiddleware: config => { - // Perform customizations to webpack dev middleware config - // Important: return the modified config - return config; - } -}; -``` - -`webpack`的第二个参数是个对象,你可以自定义配置它,对象属性如下所示: - -- `buildId` - 字符串类型,构建的唯一标示 -- `dev` - `Boolean`型,判断你是否在开发环境下 -- `isServer` - `Boolean` 型,为`true`使用在服务端, 为`false`使用在客户端. -- `defaultLoaders` - 对象型 ,内部加载器, 你可以如下配置 - - `babel` - 对象型,配置`babel-loader`. - - `hotSelfAccept` - 对象型, `hot-self-accept-loader`配置选项.这个加载器只能用于高阶案例。如 [`@zeit/next-typescript`](https://github.com/zeit/next-plugins/tree/master/packages/next-typescript)添加顶层 typescript 页面。 - -`defaultLoaders.babel`使用案例如下: - -```js -// Example next.config.js for adding a loader that depends on babel-loader -// This source was taken from the @zeit/next-mdx plugin source: -// https://github.com/zeit/next-plugins/blob/master/packages/next-mdx -module.exports = { - webpack: (config, {}) => { - config.module.rules.push({ - test: /\.mdx/, - use: [ - options.defaultLoaders.babel, - { - loader: "@mdx-js/loader", - options: pluginOptions.options - } - ] - }); - - return config; - } -}; -``` - - - -### 自定义 babel 配置 - -

- Examples - -

- -为了扩展方便我们使用`babel`,可以在应用根目录新建`.babelrc`文件,该文件可配置。 - -如果有该文件,我们将会考虑数据源,因此也需要定义 next 项目需要的东西,也就是 `next/babel`预设。 - -这种设计方案将会使你不诧异于我们可以定制 babel 配置。 - -下面是`.babelrc`文件案例: - -```json -{ - "presets": ["next/babel"], - "plugins": [] -} -``` - -`next/babel`预设可处理各种 React 应用所需要的情况。包括: - -- preset-env -- preset-react -- plugin-proposal-class-properties -- plugin-proposal-object-rest-spread -- plugin-transform-runtime -- styled-jsx - -presets / plugins 不允许添加到`.babelrc`中,然而你可以配置`next/babel`预设: - -```json -{ - "presets": [ - [ - "next/babel", - { - "preset-env": {}, - "transform-runtime": {}, - "styled-jsx": {}, - "class-properties": {} - } - ] - ], - "plugins": [] -} -``` - -`"preset-env"`模块选项应该保持为 false,否则 webpack 代码分割将被禁用。 - - - -### 暴露配置到服务端和客户端 - -在应用程序中通常需要提供配置值 - -Next.js支持2种提供配置的方式: - -- 构建时配置 -- 运行时配置 - -#### 构建时配置 - -构建时配置的工作方式是将提供的值内联到Javascript包中。 - -你可以在`next.config.js`设置`env`: - -```js -// next.config.js -module.exports = { - env: { - customKey: 'value' - } -} -``` - -这将允许你在代码中使用`process.env.customKey`,例如: - -```jsx -// pages/index.js -function Index() { - return

The value of customKey is: {process.env.customKey}

-} - -export default Index -``` - -#### 运行时配置 - -> ⚠️ 请注意,使用此选项时不可用 `target: 'serverless'` - -> ⚠️ 通常,您希望使用构建时配置来提供配置。原因是运行时配置增加了一个小的rendering/initialization开销。 - -`next/config`模块使你应用运行时可以读取些存储在`next.config.js`的配置项。`serverRuntimeConfig`属性只在服务器端可用,`publicRuntimeConfig`属性在服务端和客户端可用。 - -```js -// next.config.js -module.exports = { - serverRuntimeConfig: { - // Will only be available on the server side - mySecret: 'secret', - secondSecret: process.env.SECOND_SECRET // Pass through env variables - }, - publicRuntimeConfig: { - // Will be available on both server and client - staticFolder: '/static' - } -} -``` - -```js -// pages/index.js -import getConfig from 'next/config' -// Only holds serverRuntimeConfig and publicRuntimeConfig from next.config.js nothing else. -const { serverRuntimeConfig, publicRuntimeConfig } = getConfig() - -console.log(serverRuntimeConfig.mySecret) // Will only be available on the server side -console.log(publicRuntimeConfig.staticFolder) // Will be available on both server and client - -function MyImage() { - return ( -
- logo -
- ) -} - -export default MyImage -``` - -### 启动服务选择 hostname - -启动开发环境服务可以设置不同的 hostname,你可以在启动命令后面加上`--hostname 主机名` 或 `-H 主机名`。它将会启动一个 TCP 服务器来监听连接所提供的主机。 - - - -### CDN 支持前缀 - -建立一个 CDN,你能配置`assetPrefix`选项,去配置你的 CDN 源。 - -```js -const isProd = process.env.NODE_ENV === "production"; -module.exports = { - // You may only need to add assetPrefix in the production. - assetPrefix: isProd ? "https://cdn.mydomain.com" : "" -}; -``` - -注意:Next.js 运行时将会自动添加前缀,但是对于`/static`是没有效果的,如果你想这些静态资源也能使用 CDN,你需要自己添加前缀。有一个方法可以判断你的环境来加前缀,如 [in this example](https://github.com/zeit/next.js/tree/master/examples/with-universal-configuration-build-time)。 - - - -## 项目部署 - -部署中,你可以先构建打包生成环境代码,再启动服务。因此,构建和启动分为下面两条命令: - -```bash -next build -next start -``` - -例如,使用[`now`](https://zeit.co/now)去部署`package.json`配置文件如下: - -```json -{ - "name": "my-app", - "dependencies": { - "next": "latest" - }, - "scripts": { - "dev": "next", - "build": "next build", - "start": "next start" - } -} -``` - -然后就可以直接运行`now`了。 - -Next.js 也有其他托管解决方案。请查考 wiki 章节['Deployment'](https://github.com/zeit/next.js/wiki/Deployment) 。 - -注意:`NODE_ENV`可以通过`next`命令配置,如果没有配置,会最大渲染,如果你使用编程式写法的话[programmatically](#custom-server-and-routing),你需要手动设置`NODE_ENV=production`。 - -注意:推荐将`.next`或自定义打包文件夹[custom dist folder](https://github.com/zeit/next.js#custom-configuration)放入`.gitignore` 或 `.npmignore`中。否则,使用`files` 或 `now.files` -添加部署白名单,并排除`.next`或自定义打包文件夹。 - - - - -### 无服务器部署 - -
- 例子 - -
- -无服务器部署通过将应用程序拆分为更小的部分(也称为[**lambdas**](https://zeit.co/docs/v2/deployments/concepts/lambdas/))来显着提高可靠性和可伸缩性。在Next.js中,`pages`目录中的每个页面都变成了无服务器的lambda。 -对于无服务器的人来说,有[许多好处](https://zeit.co/blog/serverless-express-js-lambdas-with-now-2#benefits-of-serverless-express)。引用的链接在Express的上下文中讨论了其中的一些,但这些原则普遍适用:无服务器允许分布式故障点,无限的可扩展性,并且通过“为您使用的内容付费”的模式来提供难以置信的价格。 - -要在Next.js中启用**无服务器模式**,可在`Next.config.js`中配置`target`值为`serverless`: - -```js -// next.config.js -module.exports = { - target: 'serverless' -} -``` - -`serverless`将每页输出一个lambda。此文件是完全独立的,不需要运行任何依赖项: - -- `pages/index.js` => `.next/serverless/pages/index.js` -- `pages/about.js` => `.next/serverless/pages/about.js` - -Next.js无服务器功能的签名类似于Node.js HTTP服务器回调: - -```ts -export function render(req: http.IncomingMessage, res: http.ServerResponse) => void -``` - -- [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage) -- [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse) -- `void` 指的是没有返回值的函数,它等同于JavaScript`undefined`。调用该函数将完成请求。 - -使用无服务配置, 你可以讲Next.js部署到[ZEIT Now](https://zeit.co/now) 并提供所有的好处和易于控制; [custom routes](https://zeit.co/guides/custom-next-js-server-to-routes/) 缓存头. 要了解更多信息,请参阅 [ZEIT Guide for Deploying Next.js with Now](https://zeit.co/guides/deploying-nextjs-with-now/) - -#### 降级部署 - -Next.js为无服务器部署提供低级API,因为托管平台具有不同的功能签名。通常,您需要使用兼容性层包装Next.js无服务器构建的输出。 - -例如,如果平台支持Node.js[`http.Server`](https://nodejs.org/api/http.html#http_class_http_server)类: - -```js -const http = require('http') -const page = require('./.next/serverless/pages/about.js') -const server = new http.Server((req, res) => page.render(req, res)) -server.listen(3000, () => console.log('Listening on http://localhost:3000')) -``` - -有关特定平台示例,请参阅[the examples section above](#serverless-deployment). - -#### 摘要 - -- 用于实现无服务器部署的Low-level API -- `pages`目录中的每个页面都成为无服务器功能(lambda) -- 创建最小的无服务器功能 (50Kb base zip size) -- 针对功能的快速[cold start](https://zeit.co/blog/serverless-ssr#cold-start) 进行了优化 -- 无服务器函数有0个依赖项 (依赖项包含在函数包中) -- 使用Node.js中的[http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)和[http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse) -- 选择使用`target: 'serverless'` in `next.config.js` -- 在执行函数时不要加载`next.config.js`,请注意这意味着`publicRuntimeConfig` / `serverRuntimeConfig`不支持。 - -## 浏览器支持 - -Next.js 支持 IE11 和所有的现代浏览器使用了[`@babel/preset-env`](https://new.babeljs.io/docs/en/next/babel-preset-env.html)。为了支持 IE11,Next.js 需要全局添加`Promise`的 polyfill。有时你的代码或引入的其他 NPM 包的部分功能现代浏览器不支持,则需要用 polyfills 去实现。 - -ployflls 实现案例为[polyfills](https://github.com/zeit/next.js/tree/canary/examples/with-polyfills)。 - - - -## 导出静态页面 - -

- Examples - -

- -`next export`可以输出一个 Next.js 应用作为静态资源应用而不依靠 Node.js 服务。 -这个输出的应用几乎支持 Next.js 的所有功能,包括动态路由,预获取,预加载以及动态导入。 - -`next export`将把所有有可能渲染出的 HTML 都生成。这是基于映射对象的`pathname`关键字关联到页面对象。这个映射叫做`exportPathMap`。 - -页面对象有 2 个属性: - -- `page` - 字符串类型,页面生成目录 -- `query` - 对象类型,当预渲染时,`query`对象将会传入页面的生命周期`getInitialProps`中。默认为`{}`。 - - - -### 使用 - -通常开发 Next.js 应用你将会运行: - -``` -next build -next export -``` - -`next export`命令默认不需要任何配置,将会自动生成默认`exportPathMap`生成`pages`目录下的路由你页面。 - -如果你想动态配置路由,可以在`next.config.js`中添加异步函数`exportPathMap`。 - -```js -// next.config.js -module.exports = { - exportPathMap: async function(defaultPathMap) { - return { - "/": { page: "/" }, - "/about": { page: "/about" }, - "/readme.md": { page: "/readme" }, - "/p/hello-nextjs": { page: "/post", query: { title: "hello-nextjs" } }, - "/p/learn-nextjs": { page: "/post", query: { title: "learn-nextjs" } }, - "/p/deploy-nextjs": { page: "/post", query: { title: "deploy-nextjs" } } - }; - } -}; -``` - -> 注意:如果 path 的结尾是目录名,则将导出`/dir-name/index.html`,但是如果结尾有扩展名,将会导出对应的文件,如上`/readme.md`。如果你使用`.html`以外的扩展名解析文件时,你需要设置 header 的`Content-Type`头为"text/html". - -输入下面命令: - -```sh -next build -next export -``` - -你可以在`package.json`添加一个 NPM 脚本,如下所示: - -```json -{ - "scripts": { - "build": "next build", - "export": "npm run build && next export" - } -} -``` - -接着只用执行一次下面命令: - -```sh -npm run export -``` - -然后你将会有一个静态页面应用在`out` 目录下。 - -> 你也可以自定义输出目录。可以运行`next export -h`命令查看帮助。 - -现在你可以部署`out`目录到任意静态资源服务器上。注意如果部署 GitHub Pages 需要加个额外的步骤,[文档如下](https://github.com/zeit/next.js/wiki/Deploying-a-Next.js-app-into-GitHub-Pages) - -例如,访问`out`目录并用下面命令部署应用[ZEIT Now](https://zeit.co/now). - -```sh -now -``` - - - -### 复制自定义文件 - -如果您必须复制robots.txt等自定义文件或生成sitemap.xml,您可以在其中执行此操作`exportPathMap`。 `exportPathMap`获取一些上下文参数来帮助您创建/复制文件: - -- `dev` - `true`表示在开发环境下使用`exportPathMap`. `false`表示运行于`next export`. 在开发中,“exportpathmap”用于定义路由,不需要复制文件等行为。 -- `dir` - 项目目录的绝对路径 -- `outDir` - 指向`out`目录的绝对路径(可配置为`-o`或`--outdir`)。当`dev`为`true`时,`outdir`的值将为`null`。 -- `distDir` - `.next`目录的绝对路径(可使用`distDir`配置键配置) -- `buildId` - 导出正在运行的buildId - -```js -// next.config.js -const fs = require('fs') -const { join } = require('path') -const { promisify } = require('util') -const copyFile = promisify(fs.copyFile) - -module.exports = { - exportPathMap: async function( - defaultPathMap, - { dev, dir, outDir, distDir, buildId } - ) { - if (dev) { - return defaultPathMap - } - // This will copy robots.txt from your project root into the out directory - await copyFile(join(dir, 'robots.txt'), join(outDir, 'robots.txt')) - return defaultPathMap - } -} -``` - -### 限制 - -使用`next export`,我们创建了个静态 HTML 应用。构建时将会运行页面里生命周期`getInitialProps` 函数。 - -`req`和`res`只在服务端可用,不能通过`getInitialProps`。 - -> 所以你不能预构建 HTML 文件时动态渲染 HTML 页面。如果你想动态渲染可以运行`next start`或其他自定义服务端 API。 - - - -## 多 zone - -

- Examples - -

- -一个 zone 时一个单独的 Next.js 应用。如果你有很多 zone,你可以合并成一个应用。 - -例如,你如下有两个 zone: - -- https://docs.my-app.com 服务于路由 `/docs/**` -- https://ui.my-app.com 服务于所有页面 - -有多 zone 应用技术支持,你可以将几个应用合并到一个,而且可以自定义 URL 路径,使你能同时单独开发各个应用。 - -> 与 microservices 观念类似, 只是应用于前端应用. - - - -### 怎么定义一个 zone - -zone 没有单独的 API 文档。你需要做下面事即可: - -- 确保你的应用里只有需要的页面 (例如, https://ui.my-app.com 不包含 `/docs/**`) -- 确保你的应用有个前缀[assetPrefix](https://github.com/zeit/next.js#cdn-support-with-asset-prefix)。(你也可以定义动态前缀[dynamically](https://github.com/zeit/next.js#dynamic-assetprefix)) - - - -### 怎么合并他们 - -你能使用 HTTP 代理合并 zone - -你能使用代理[micro proxy](https://github.com/zeit/micro-proxy)来作为你的本地代理服务。它允许你定义路由规则如下: - -```json -{ - "rules": [ - { - "pathname": "/docs**", - "method": ["GET", "POST", "OPTIONS"], - "dest": "https://docs.my-app.com" - }, - { "pathname": "/**", "dest": "https://ui.my-app.com" } - ] -} -``` - -生产环境部署,如果你使用了[ZEIT now](https://zeit.co/now),可以它的使用[path alias](https://zeit.co/docs/features/path-aliases) 功能。否则,你可以设置你已使用的代理服务编写上面规则来路由 HTML 页面 - - - -## 技巧 - -- [设置 301 重定向](https://www.raygesualdo.com/posts/301-redirects-with-nextjs/) -- [只处理服务器端模块](https://arunoda.me/blog/ssr-and-server-only-modules) -- [构建项目 React-Material-UI-Next-Express-Mongoose-Mongodb](https://github.com/builderbook/builderbook) -- [构建一个 SaaS 产品 React-Material-UI-Next-MobX-Express-Mongoose-MongoDB-TypeScript](https://github.com/async-labs/saas) - - - -## 问答 - -
- 这个产品可以用于生产环境吗? - https://zeit.co 都是一直用 Next.js 写的。 - -它的开发体验和终端用户体验都很好,所以我们决定开源出来给大家共享。 - -
- -
- 体积多大? - -客户端大小根据应用需求不一样大小也不一样。 - -一个最简单 Next 应该用 gzip 压缩后大约 65kb - -
- -
- 这个像 `create-react-app`? - -是或不是. - -是,因为它让你的 SSR 开发更简单。 - -不是,因为它规定了一定的目录结构,使我们能做以下更高级的事: - -- 服务端渲染 -- 自动代码分割 - -此外,Next.js 还提供两个内置特性: - -- 路由与懒加载组件: `` (通过引入 `next/link`) -- 修改``的组件: `` (通过引入 `next/head`) - -如果你想写共用组件,可以嵌入 Next.js 应用和 React 应用中,推荐使用`create-react-app`。你可以更改`import`保持代码清晰。 - -
- -
- 怎么解决 CSS 嵌入 JS 问题? - -Next.js 自带[styled-jsx](https://github.com/zeit/styled-jsx)库支持 CSS 嵌入 JS。而且你可以选择其他嵌入方法到你的项目中,可参考文档[as mentioned before](#css-in-js)。 - -
- -
- 哪些语法会被转换?怎么转换它们? - -我们遵循 V8 引擎的,如今 V8 引擎广泛支持 ES6 语法以及`async`和`await`语法,所以我们支持转换它们。但是 V8 引擎不支持修饰器语法,所以我们也不支持转换这语法。 - -可以参照[这些](https://github.com/zeit/next.js/blob/master/server/build/webpack.js#L79) 以及 [这些](https://github.com/zeit/next.js/issues/26) - -
- -
- 为什么使用新路由? - -Next.js 的特别之处如下所示: - -- 路由不需要被提前知道 -- 路由总是被懒加载 -- 顶层组件可以定义生命周期`getInitialProps`来阻止路由加载(当服务端渲染或路由懒加载时) - -因此,我们可以介绍一个非常简单的路由方法,它由下面两部分组成: - -- 每个顶层组件都将会收到一个`url`对象,来检查 url 或修改历史记录 -- ``组件用于包装如(``)标签的元素容器,来执行客户端转换。 - -我们使用了些有趣的场景来测试路由的灵活性,例如,可查看[nextgram](https://github.com/zeit/nextgram)。 - -
- -
-我怎么定义自定义路由? - -我们通过请求处理来[添加](#custom-server-and-routing)任意 URL 与任意组件之前的映射关系。 - -在客户端,我们``组件有个属性`as`,可以装饰改变获取到的 URL。 - -
- -
-怎么获取数据? - -这由你决定。`getInitialProps`是一个异步函数`async`(也就是函数将会返回个`Promise`)。你可以在任意位置获取数据。 - -
- -
- 我可以使用 GraphQL 吗? - -是的! 这里有个例子[Apollo](./examples/with-apollo). - -
- -
-我可以使用 Redux 吗? - -是的! 这里有个[例子](./examples/with-redux) - -
- -
-我可以在 Next 应用中使用我喜欢的 Javascript 库或工具包吗? - -从我们第一次发版就已经提供**很多**例子,你可以查看这些[例子](./examples)。 - -
- -
-什么启发我们做这个? - -我们实现的大部分目标都是通过 Guillermo Rauch 的[Web 应用的 7 原则](http://rauchg.com/2014/7-principles-of-rich-web-applications/)来启发出的。 - -PHP 的易用性也是个很好的灵感来源,我们觉得 Next.js 可以替代很多需要用 PHP 输出 HTML 的场景。 - -与 PHP 不同的是,我们得利于 ES6 模块系统,每个文件会输出一个**组件或方法**,以便可以轻松的导入用于懒加载和测试 - -我们研究 React 的服务器渲染时并没有花费很大的步骤,因为我们发现一个类似于 Next.js 的产品,React 作者 Jordan Walke 写的[react-page](https://github.com/facebookarchive/react-page) (现在已经废弃) - -
- -
- -## 贡献 - -可查看 [contributing.md](./contributing.md) - - - -## 作者 - -- Arunoda Susiripala ([@arunoda](https://twitter.com/arunoda)) – [ZEIT](https://zeit.co) -- Tim Neutkens ([@timneutkens](https://twitter.com/timneutkens)) – [ZEIT](https://zeit.co) -- Naoyuki Kanezawa ([@nkzawa](https://twitter.com/nkzawa)) – [ZEIT](https://zeit.co) -- Tony Kovanen ([@tonykovanen](https://twitter.com/tonykovanen)) – [ZEIT](https://zeit.co) -- Guillermo Rauch ([@rauchg](https://twitter.com/rauchg)) – [ZEIT](https://zeit.co) -- Dan Zajdband ([@impronunciable](https://twitter.com/impronunciable)) – Knight-Mozilla / Coral Project diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 00000000000000..83373e6ae0ad5d --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1 @@ +This document has been moved to [nextjs.org/docs/upgrading](https://nextjs.org/docs/upgrading). It's also available in this repository on [/docs/upgrading.md](/docs/upgrading.md). diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 74b306bf0e683f..40e8b3f12e1e45 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,24 +1,66 @@ -pool: - vmImage: 'vs2017-win2016' - -strategy: - maxParallel: 10 - matrix: - node-10: - node_version: ^10.10.0 - node-8: - node_version: ^8.12.0 - -steps: -- task: NodeTool@0 - inputs: - versionSpec: $(node_version) - displayName: 'Install Node.js' - -- script: | - yarn install - displayName: 'Install dependencies' - -- script: | - yarn test - displayName: 'Run tests' +variables: + YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn + NEXT_TELEMETRY_DISABLED: '1' + node_version: ^10.10.0 + +jobs: + - job: test_ie11 + pool: + vmImage: 'windows-2019' + steps: + - task: NodeTool@0 + inputs: + versionSpec: $(node_version) + displayName: 'Install Node.js' + + - task: CacheBeta@0 + inputs: + key: yarn | $(Agent.OS) | yarn.lock + path: $(YARN_CACHE_FOLDER) + displayName: Cache Yarn packages + + - script: | + yarn install --frozen-lockfile --check-files + displayName: 'Install dependencies' + + - script: | + yarn testie --forceExit test/integration/production/ + displayName: 'Run tests' + + - job: test_chrome + pool: + vmImage: 'windows-2019' + strategy: + maxParallel: 10 + matrix: + node-10-1: + group: 1/4 + node-10-2: + group: 2/4 + node-10-3: + group: 3/4 + node-10-4: + group: 4/4 + steps: + - script: | + wmic datafile where name="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" get Version /value + displayName: 'List Chrome version' + + - task: NodeTool@0 + inputs: + versionSpec: $(node_version) + displayName: 'Install Node.js' + + - task: CacheBeta@0 + inputs: + key: yarn | $(Agent.OS) | yarn.lock + path: $(YARN_CACHE_FOLDER) + displayName: Cache Yarn packages + + - script: | + yarn install --frozen-lockfile --check-files + displayName: 'Install dependencies' + + - script: | + node run-tests.js -g $(group) --timings + displayName: 'Run tests' diff --git a/bench/package.json b/bench/package.json index d39122c7c99766..eb3c593c98f8cf 100644 --- a/bench/package.json +++ b/bench/package.json @@ -4,6 +4,11 @@ "build": "next build", "start": "NODE_ENV=production npm run build && NODE_ENV=production next start", "bench:stateless": "ab -c1 -n3000 http://0.0.0.0:3000/stateless", - "bench:stateless-big": "ab -c1 -n500 http://0.0.0.0:3000/stateless-big" + "bench:stateless-big": "ab -c1 -n500 http://0.0.0.0:3000/stateless-big", + "bench:recursive-copy": "node recursive-copy/run" + }, + "dependencies": { + "fs-extra": "7.0.1", + "recursive-copy": "2.0.10" } } diff --git a/bench/pages/stateless-big.js b/bench/pages/stateless-big.js index 5b14df331c2f62..87f340d66d7e9c 100644 --- a/bench/pages/stateless-big.js +++ b/bench/pages/stateless-big.js @@ -1,11 +1,7 @@ import React from 'react' export default () => { - return ( -
    - {items()} -
- ) + return
    {items()}
} const items = () => { diff --git a/bench/pages/stateless.js b/bench/pages/stateless.js index 256b5e8f006c89..620ef04822f5f1 100644 --- a/bench/pages/stateless.js +++ b/bench/pages/stateless.js @@ -1 +1,2 @@ +import React from 'react' export default () =>

My component!

diff --git a/bench/readdir/glob.js b/bench/readdir/glob.js index b871f0779d6951..1c409ad384ba50 100644 --- a/bench/readdir/glob.js +++ b/bench/readdir/glob.js @@ -4,17 +4,17 @@ const globMod = require('glob') const glob = promisify(globMod) const resolveDataDir = join(__dirname, 'fixtures', '**/*') -async function test () { +async function test() { const time = process.hrtime() await glob(resolveDataDir) const hrtime = process.hrtime(time) - const nanoseconds = (hrtime[0] * 1e9) + hrtime[1] + const nanoseconds = hrtime[0] * 1e9 + hrtime[1] const milliseconds = nanoseconds / 1e6 console.log(milliseconds) } -async function run () { +async function run() { for (let i = 0; i < 50; i++) { await test() } diff --git a/bench/readdir/recursive-readdir.js b/bench/readdir/recursive-readdir.js index 43877bb73a0b70..871256707ddb6f 100644 --- a/bench/readdir/recursive-readdir.js +++ b/bench/readdir/recursive-readdir.js @@ -2,17 +2,17 @@ const { join } = require('path') const { recursiveReadDir } = require('next/dist/lib/recursive-readdir') const resolveDataDir = join(__dirname, 'fixtures') -async function test () { +async function test() { const time = process.hrtime() await recursiveReadDir(resolveDataDir, /\.js$/) const hrtime = process.hrtime(time) - const nanoseconds = (hrtime[0] * 1e9) + hrtime[1] + const nanoseconds = hrtime[0] * 1e9 + hrtime[1] const milliseconds = nanoseconds / 1e6 console.log(milliseconds) } -async function run () { +async function run() { for (let i = 0; i < 50; i++) { await test() } diff --git a/bench/readme.md b/bench/readme.md index 0ac01e4b1b4265..71c7070d8f691c 100644 --- a/bench/readme.md +++ b/bench/readme.md @@ -17,11 +17,13 @@ npm run start Then run one of these tests: - Stateless application which renders `

My component!

`. Runs 3000 http requests. + ``` npm run bench:stateless ``` - Stateless application which renders `
  • This is row {i}
  • ` 10.000 times. Runs 500 http requests. + ``` npm run bench:stateless-big ``` diff --git a/bench/recursive-copy/run.js b/bench/recursive-copy/run.js new file mode 100644 index 00000000000000..adff8be65aa42c --- /dev/null +++ b/bench/recursive-copy/run.js @@ -0,0 +1,63 @@ +const { join } = require('path') +const fs = require('fs-extra') + +const recursiveCopyNpm = require('recursive-copy') + +const { + recursiveCopy: recursiveCopyCustom, +} = require('next/dist/lib/recursive-copy') + +const fixturesDir = join(__dirname, 'fixtures') +const srcDir = join(fixturesDir, 'src') +const destDir = join(fixturesDir, 'dest') + +const createSrcFolder = async () => { + await fs.ensureDir(srcDir) + + const files = new Array(100) + .fill(undefined) + .map((x, i) => + join(srcDir, `folder${i % 5}`, `folder${i + (1 % 5)}`, `file${i}`) + ) + + await Promise.all(files.map(file => fs.outputFile(file, 'hello'))) +} + +async function run(fn) { + async function test() { + const start = process.hrtime() + + await fn(srcDir, destDir) + + const timer = process.hrtime(start) + const ms = (timer[0] * 1e9 + timer[1]) / 1e6 + return ms + } + + const ts = [] + + for (let i = 0; i < 10; i++) { + const t = await test() + await fs.remove(destDir) + ts.push(t) + } + + const sum = ts.reduce((a, b) => a + b) + const nb = ts.length + const avg = sum / nb + console.log({ sum, nb, avg }) +} + +async function main() { + await createSrcFolder() + + console.log('test recursive-copy npm module') + await run(recursiveCopyNpm) + + console.log('test recursive-copy custom implementation') + await run(recursiveCopyCustom) + + await fs.remove(fixturesDir) +} + +main() diff --git a/bench/recursive-delete/recursive-delete.js b/bench/recursive-delete/recursive-delete.js index 8b43044e396d41..8989739c9039f1 100644 --- a/bench/recursive-delete/recursive-delete.js +++ b/bench/recursive-delete/recursive-delete.js @@ -2,12 +2,12 @@ const { join } = require('path') const { recursiveDelete } = require('next/dist/lib/recursive-delete') const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`) -async function test () { +async function test() { const time = process.hrtime() await recursiveDelete(resolveDataDir) const hrtime = process.hrtime(time) - const nanoseconds = (hrtime[0] * 1e9) + hrtime[1] + const nanoseconds = hrtime[0] * 1e9 + hrtime[1] const milliseconds = nanoseconds / 1e6 console.log(milliseconds) } diff --git a/bench/recursive-delete/rimraf.js b/bench/recursive-delete/rimraf.js index d3163afc970061..2b5d50457a13c8 100644 --- a/bench/recursive-delete/rimraf.js +++ b/bench/recursive-delete/rimraf.js @@ -4,12 +4,12 @@ const rimrafMod = require('rimraf') const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`, '**/*') const rimraf = promisify(rimrafMod) -async function test () { +async function test() { const time = process.hrtime() await rimraf(resolveDataDir) const hrtime = process.hrtime(time) - const nanoseconds = (hrtime[0] * 1e9) + hrtime[1] + const nanoseconds = hrtime[0] * 1e9 + hrtime[1] const milliseconds = nanoseconds / 1e6 console.log(milliseconds) } diff --git a/contributing.md b/contributing.md index 2d9a0da2ddde64..907253d4ba24c8 100644 --- a/contributing.md +++ b/contributing.md @@ -3,52 +3,169 @@ Our Commitment to Open Source can be found [here](https://zeit.co/blog/oss) 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. -1. Install yarn: `npm install -g yarn` -1. Install the dependencies: `yarn` -1. Run `yarn run dev` to build and watch for code changes +2. Create a new branch `git checkout -b MY_BRANCH_NAME` +3. Install yarn: `npm install -g yarn` +4. Install the dependencies: `yarn` +5. Run `yarn dev` to build and watch for code changes +6. In a new terminal, run `yarn types` to compile declaration files from TypeScript +7. The development branch is `canary` (this is the branch pull requests should be made against). On a release, the relevant parts of the changes in the `canary` branch are rebased into `master`. + +> You may need to run `yarn types` again if your types get outdated. + +To contribute to [our examples](examples), take a look at the [“Adding examples” section](#adding-examples). ## To run tests +Make sure you have `chromedriver` installed for your Chrome version. You can install it with + +- `brew cask install chromedriver` on Mac OS X +- `chocolatey install chromedriver` on Windows +- Or manually downloading it from the [chromedriver repo](https://chromedriver.storage.googleapis.com/index.html) and adding the binary to `/node_modules/.bin` + Running all tests: -``` +```sh yarn testonly ``` -Running a specific test suite inside of the `test/integration` directory: +If you would like to run the tests in headless mode (with the browser windows hidden) you can do +```sh +yarn testheadless ``` + +If you would like to use a specific Chrome/Chromium binary to run tests you can specify it with + +```sh +CHROME_BIN='path/to/chrome/bin' yarn testonly +``` + +Running a specific test suite inside of the `test/integration` directory: + +```sh yarn testonly --testPathPattern "production" ``` Running just one test in the `production` test suite: -``` +```sh yarn testonly --testPathPattern "production" -t "should allow etag header support" ``` -## Running the integration test apps without running tests +## Running the integration apps + +Running examples can be done with: +```sh +yarn next ./test/integration/basic +# OR +yarn next ./examples/basic-css/ ``` -./node_modules/.bin/next ./test/integration/basic + +To figure out which pages are available for the given example, you can run: + +```sh +EXAMPLE=./test/integration/basic +(\ + cd $EXAMPLE/pages; \ + find . -type f \ + | grep -v '\.next' \ + | sed 's#^\.##' \ + | sed 's#index\.js##' \ + | sed 's#\.js$##' \ + | xargs -I{} echo localhost:3000{} \ +) ``` -## Testing in your own app +## Running your own app with locally compiled version of Next.js -Because of the way Node.js resolves modules the easiest way to test your own application is copying it into the `test` directory. +1. In your app's `package.json`, replace: -``` -cp -r yourapp /test/integration/yourapp -``` + ```json + "next": "", + ``` + + with: + + ```json + "next": "file:/packages/next", + ``` + +2. In your app's root directory, make sure to remove `next` from `node_modules` with: + + ```sh + rm -rf ./node_modules/next + ``` + +3. In your app's root directory, run: -Make sure you remove `react` `react-dom` and `next` from `test/integration/yourapp/node_modules` as otherwise they will be overwritten. + ```sh + yarn + ``` + + to re-install all of the dependencies. + + Note that Next will be copied from the locally compiled version as opposed to from being downloaded from the NPM registry. + +4. Run your application as you normally would. + +5. To update your app's dependencies, after you've made changes to your local `next` repository. In your app's root directory, run: + + ```sh + yarn install --force + ``` + +## Adding examples + +When you add an example to the [examples](examples) directory, don’t forget to add a `README.md` file with the following format: + +- Replace `DIRECTORY_NAME` with the directory name you’re adding. +- Fill in `Example Name` and `Description`. +- To add additional installation instructions, please add it where appropriate. +- To add additional notes, add `## Notes` section at the end. +- Remove the `Deploy your own` section if your example can’t be immediately deployed to ZEIT Now. + +````markdown +# Example Name + +Description + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/DIRECTORY_NAME) + +## How to use + +### Using `create-next-app` + +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -rm -rf /test/integration/yourapp/{react,react-dom,next,next-server} +npx create-next-app --example DIRECTORY_NAME DIRECTORY_NAME-app +# or +yarn create next-app --example DIRECTORY_NAME DIRECTORY_NAME-app ``` -Then run your app using: +### Download manually + +Download the example: +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/DIRECTORY_NAME +cd DIRECTORY_NAME ``` -./node_modules/.bin/next ./test/integration/yourapp + +Install it and run: + +```bash +npm install +npm run dev +# or +yarn +yarn dev ``` + +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). +```` diff --git a/docs/advanced-features/amp-support/adding-amp-components.md b/docs/advanced-features/amp-support/adding-amp-components.md new file mode 100644 index 00000000000000..63305791d54488 --- /dev/null +++ b/docs/advanced-features/amp-support/adding-amp-components.md @@ -0,0 +1,70 @@ +--- +description: Add components from the AMP community to AMP pages, and make your pages more interactive. +--- + +# Adding AMP Components + +The AMP community provides [many components](https://amp.dev/documentation/components/) to make AMP pages more interactive. Next.js will automatically import all components used on a page and there is no need to manually import AMP component scripts: + +```jsx +export const config = { amp: true } + +function MyAmpPage() { + const date = new Date() + + return ( +
    +

    Some time: {date.toJSON()}

    + + . + +
    + ) +} + +export default MyAmpPage +``` + +The above example uses the [`amp-timeago`](https://amp.dev/documentation/components/amp-timeago/?format=websites) component. + +By default, the latest version of a component is always imported. If you want to customize the version, you can use `next/head`, as in the following example: + +```jsx +import Head from 'next/head' + +export const config = { amp: true } + +function MyAmpPage() { + const date = new Date() + + return ( +
    + + + + ``` + + Instead you need to use `dangerouslySetInnerHTML` to initialize the string. can use the [`/components/amp/AmpState.js`](components/amp/AmpState.js) component to see how it works. The same approach works for `amp-access` and `amp-consent` as well: + + ```html + + ``` + +- **amp-list & amp-mustache:** mustache templates conflict with JSX and it's template literals need to be escaped. A simple approach is to escape them via back ticks: `` src={`{{imageUrl}}`} ``. +- **amp-script:** you can use [amp-script](https://amp.dev/documentation/components/amp-script/) to add custom JavaScript to your AMP pages. The boilerplate includes a helper [`components/amp/AmpScript.js`](components/amp/AmpScript.js) to simplify using amp-script. The helper also supports embedding inline scripts. Good to know: Next.js uses [AMP Optimizer](https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer) under the hood, which automatically adds the needed script hashes for [inline amp-scripts](https://amp.dev/documentation/components/amp-script/#load-javascript-from-a-local-element). + +## Deployment + +For production builds, you need to run (the app will be build into the `.next` folder): + +```shell +$ yarn build +``` + +To start the application in production mode, run: + +```shell +$ yarn start +``` + +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/amp-first/components/Layout.js b/examples/amp-first/components/Layout.js new file mode 100644 index 00000000000000..6abebffa4d3764 --- /dev/null +++ b/examples/amp-first/components/Layout.js @@ -0,0 +1,54 @@ +import PropTypes from 'prop-types' +import NextHead from 'next/head' +import { AmpIncludeAmpInstallServiceworker } from './amp/AmpCustomElement' + +// Your app's theme color +const THEME_COLOR = '#005af0' + +/** + * A sample page layout installing the AMP Serviceworker by default. + * + * @param {Props} props + */ +const Layout = props => ( + <> + + {props.title || ''} + + + + + + + + + {props.children} + + + + + + +) + +Layout.propTypes = { + title: PropTypes.string, + description: PropTypes.string, +} + +export default Layout diff --git a/examples/amp-first/components/amp/AmpCustomElement.js b/examples/amp-first/components/amp/AmpCustomElement.js new file mode 100644 index 00000000000000..d099e8a114952f --- /dev/null +++ b/examples/amp-first/components/amp/AmpCustomElement.js @@ -0,0 +1,531 @@ +/** + * @file An AMP Component import helper. This file is auto-generated using + * https://www.npmjs.com/package/@ampproject/toolbox-validator-rules. + */ +import React from 'react' +import Head from 'next/head' + +export function AmpIncludeCustomElement(props) { + return ( + + +`} + Instead you need to use dangerouslySetInnerHTML to + initialize the string. can use the{' '} + /components/amp/AmpState.js component to see how it + works. The same approach works for amp-access and{' '} + amp-consent as well + + + Demo: +

    + + + {{ + message: 'Hello World', + }} + + + + + +
    +

    amp-list & amp-mustache

    +

    + Mustache templates conflict with JSX and it's template literals need + to be escaped. A simple approach is to escape them via back ticks:{' '} + src={`{{imageUrl}}`}. +

    + + + + + +
    + +
    +

    amp-script

    +

    + Checkout the{' '} + + amp-script + {' '} + helper here: components/amp/AmpScript.js for embedding + custom JavaScript. +

    + + + + + +

    + The helper also supports embedding inline scripts. Good to know: + Next.js uses{' '} + + AMP Optimizer + {' '} + under the hood, which automatically adds the needed script hashes + for{' '} + + inline amp-scripts + + . +

    + + + +
    +
    + + + +) + +// amp-script requires absolute URLs, so we create a property `host` which we can use to calculate the script URL. +export async function getServerSideProps({ req }) { + // WARNING: This is a generally unsafe application unless you're deploying to a managed platform like ZEIT Now. + // Be sure your load balancer is configured to not allow spoofed host headers. + return { props: { host: `${getProtocol(req)}://${req.headers.host}` } } +} + +function getProtocol(req) { + if (req.connection.encrypted) { + return 'https' + } + const forwardedProto = req.headers['x-forwarded-proto'] + if (forwardedProto) { + return forwardedProto.split(/\s*,\s*/)[0] + } + return 'http' +} + +export default Home diff --git a/examples/amp-first/pages/offline.js b/examples/amp-first/pages/offline.js new file mode 100644 index 00000000000000..9a31b13122de44 --- /dev/null +++ b/examples/amp-first/pages/offline.js @@ -0,0 +1,13 @@ +import React from 'react' +import Layout from '../components/Layout' + +export const config = { amp: true } + +const Home = () => ( + +

    Offline

    +

    Please try again later.

    +
    +) + +export default Home diff --git a/examples/amp-first/public/install-serviceworker.html b/examples/amp-first/public/install-serviceworker.html new file mode 100644 index 00000000000000..cffb15dd6caa82 --- /dev/null +++ b/examples/amp-first/public/install-serviceworker.html @@ -0,0 +1,7 @@ + +installing service worker + diff --git a/examples/amp-first/public/manifest.json b/examples/amp-first/public/manifest.json new file mode 100644 index 00000000000000..a4371e83d0db70 --- /dev/null +++ b/examples/amp-first/public/manifest.json @@ -0,0 +1,20 @@ +{ + "short_name": "My page", + "name": "My fantastic page", + "icons": [ + { + "src": "/static/images/icons-192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "/static/images/icons-512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "display": "standalone", + "scope": "/", + "theme_color": "#005af0", + "background_color": "#ffffff" +} diff --git a/examples/amp-first/public/serviceworker.js b/examples/amp-first/public/serviceworker.js new file mode 100644 index 00000000000000..d5f58a9fda3a6d --- /dev/null +++ b/examples/amp-first/public/serviceworker.js @@ -0,0 +1,28 @@ +/* global importScripts, AMP_SW */ +importScripts('https://cdn.ampproject.org/sw/amp-sw.js') + +/* + This configures the AMP service worker to enhance network resiliency and + optimizes asset caching. This configuration will: + + - Cache AMP scripts with a stale-while-revalidate strategy for a longer duration + than the default http response headers indicate. + - Cache valid visited AMP documents, and serve only in case of flaky network conditions. + - Cache and serve an offline page. + - Serve static assets with a cache first strategy. + + Checkout https://github.com/ampproject/amp-sw/ to learn more about how to configure + asset caching and link prefetching. +*/ +AMP_SW.init({ + assetCachingOptions: [ + { + regexp: /\.(png|jpg|woff2|woff|css|js)/, + cachingStrategy: 'CACHE_FIRST', // options are NETWORK_FIRST | CACHE_FIRST | STALE_WHILE_REVALIDATE + }, + ], + offlinePageOptions: { + url: '/offline', + assets: [], + }, +}) diff --git a/examples/amp-first/public/static/amp-script/hello.js b/examples/amp-first/public/static/amp-script/hello.js new file mode 100644 index 00000000000000..448ebac5466fa5 --- /dev/null +++ b/examples/amp-first/public/static/amp-script/hello.js @@ -0,0 +1,4 @@ +const btn = document.querySelector('button') +btn.addEventListener('click', () => { + document.body.textContent = 'Hello World!' +}) diff --git a/examples/with-algolia-react-instantsearch/static/favicon.ico b/examples/amp-first/public/static/favicon.ico similarity index 100% rename from examples/with-algolia-react-instantsearch/static/favicon.ico rename to examples/amp-first/public/static/favicon.ico diff --git a/examples/amp-first/public/static/images/icons-192.png b/examples/amp-first/public/static/images/icons-192.png new file mode 100755 index 00000000000000..b6405c4c0e6f66 Binary files /dev/null and b/examples/amp-first/public/static/images/icons-192.png differ diff --git a/examples/amp-first/public/static/images/icons-512.png b/examples/amp-first/public/static/images/icons-512.png new file mode 100755 index 00000000000000..dd1ff4acfde534 Binary files /dev/null and b/examples/amp-first/public/static/images/icons-512.png differ diff --git a/examples/amp-story/.gitignore b/examples/amp-story/.gitignore new file mode 100644 index 00000000000000..1f1d31514a8f23 --- /dev/null +++ b/examples/amp-story/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.next +node_modules diff --git a/examples/amp-story/README.md b/examples/amp-story/README.md new file mode 100644 index 00000000000000..6e05c2d42bd751 --- /dev/null +++ b/examples/amp-story/README.md @@ -0,0 +1,42 @@ +# Google AMP Story + +This example shows how to create an AMP page with `amp-story` using Next.js and the AMP feature. + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/amp-story) + +## How to use + +### Using `create-next-app` + +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npm init next-app --example amp-story amp-app +# or +yarn create next-app --example amp-story amp-app +``` + +### Download manually + +Download the example: + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/amp-story +cd amp-story +``` + +Install it and run: + +```bash +npm install +npm run dev +# or +yarn +yarn dev +``` + +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/amp-story/package.json b/examples/amp-story/package.json new file mode 100644 index 00000000000000..c5b61de231dd7a --- /dev/null +++ b/examples/amp-story/package.json @@ -0,0 +1,15 @@ +{ + "name": "amp-story", + "version": "1.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "^16.7.0", + "react-dom": "^16.7.0" + }, + "license": "ISC" +} diff --git a/examples/amp-story/pages/index.js b/examples/amp-story/pages/index.js new file mode 100644 index 00000000000000..d7b9e05dcca312 --- /dev/null +++ b/examples/amp-story/pages/index.js @@ -0,0 +1,123 @@ +import Head from 'next/head' + +export const config = { amp: true } + +export default () => ( + <> + + Example AMP Story in Next.js + + + + + + + diff --git a/examples/with-netlify-cms/public/static/img/1200px-whio_blue_duck_at_staglands_akatarawa_new_zealand.jpg b/examples/with-netlify-cms/public/static/img/1200px-whio_blue_duck_at_staglands_akatarawa_new_zealand.jpg new file mode 100644 index 00000000000000..44c1757cbc0681 Binary files /dev/null and b/examples/with-netlify-cms/public/static/img/1200px-whio_blue_duck_at_staglands_akatarawa_new_zealand.jpg differ diff --git a/examples/with-netlify-cms/public/static/img/puppy-and-adult-dog.jpg b/examples/with-netlify-cms/public/static/img/puppy-and-adult-dog.jpg new file mode 100644 index 00000000000000..9a1b54c547dd50 Binary files /dev/null and b/examples/with-netlify-cms/public/static/img/puppy-and-adult-dog.jpg differ diff --git a/examples/with-next-css/README.md b/examples/with-next-css/README.md index 73d19f7e34a079..c2460f1e14c8e9 100644 --- a/examples/with-next-css/README.md +++ b/examples/with-next-css/README.md @@ -1,15 +1,21 @@ -[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-next-css) - # next-css example +This example demonstrates how to use Next.js' built-in CSS imports and CSS modules support. + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-next-css) + ## How to use ### Using `create-next-app` -Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -npx create-next-app --example with-next-css with-next-css-app +npm init next-app --example with-next-css with-next-css-app # or yarn create next-app --example with-next-css with-next-css-app ``` @@ -33,12 +39,4 @@ yarn yarn dev ``` -Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) - -```bash -now -``` - -## The idea behind the example - -This example demonstrates how to use the [next-css plugin](https://github.com/zeit/next-plugins/tree/master/packages/next-css) It includes patterns for with and without CSS Modules, with PostCSS and with additional webpack configurations on top of the next-css plugin. \ No newline at end of file +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-next-css/component/hello-world.js b/examples/with-next-css/component/hello-world.js new file mode 100644 index 00000000000000..bff5163912f794 --- /dev/null +++ b/examples/with-next-css/component/hello-world.js @@ -0,0 +1,7 @@ +import css from './hello-world.module.css' + +export default () => ( +
    + Hello World, I am being styled using CSS Modules! +
    +) diff --git a/examples/with-next-css/component/hello-world.module.css b/examples/with-next-css/component/hello-world.module.css new file mode 100644 index 00000000000000..5c5ef1d901eefa --- /dev/null +++ b/examples/with-next-css/component/hello-world.module.css @@ -0,0 +1,3 @@ +.hello { + font-size: 25px; +} diff --git a/examples/with-next-css/next.config.js b/examples/with-next-css/next.config.js deleted file mode 100644 index b631652547704c..00000000000000 --- a/examples/with-next-css/next.config.js +++ /dev/null @@ -1,16 +0,0 @@ -const withCSS = require('@zeit/next-css') -/* Without CSS Modules, with PostCSS */ -module.exports = withCSS() - -/* With CSS Modules */ -// module.exports = withCSS({ cssModules: true }) - -/* With additional configuration on top of CSS Modules */ -/* -module.exports = withCSS({ - cssModules: true, - webpack: function (config) { - return config; - } -}); -*/ diff --git a/examples/with-next-css/package.json b/examples/with-next-css/package.json index bdb8c509e1cda1..fb20839d6b9651 100644 --- a/examples/with-next-css/package.json +++ b/examples/with-next-css/package.json @@ -8,8 +8,7 @@ "start": "next start" }, "dependencies": { - "@zeit/next-css": "^1.0.1", - "next": "latest", + "next": "^9.1.8-canary.11", "react": "^16.7.0", "react-dom": "^16.7.0" } diff --git a/examples/with-next-css/pages/_app.js b/examples/with-next-css/pages/_app.js new file mode 100644 index 00000000000000..e94667d6f631ed --- /dev/null +++ b/examples/with-next-css/pages/_app.js @@ -0,0 +1,7 @@ +import '../style.css' + +function MyApp({ Component, pageProps }) { + return +} + +export default MyApp diff --git a/examples/with-next-css/pages/index.js b/examples/with-next-css/pages/index.js index b856ab5442851e..6d95978da3265f 100644 --- a/examples/with-next-css/pages/index.js +++ b/examples/with-next-css/pages/index.js @@ -1,12 +1,7 @@ -/* Without CSS Modules, maybe with PostCSS */ +import HelloWorld from '../component/hello-world' -import '../style.css' - -export default () =>
    O Hai world!
    - -/* With CSS Modules */ -/* -import css from "../style.css" - -export default () =>
    Hello World, I am being styled using CSS Modules!
    -*/ +export default () => ( +
    + +
    +) diff --git a/examples/with-next-css/style.css b/examples/with-next-css/style.css index d59a9112f77011..ac225d85159ac2 100644 --- a/examples/with-next-css/style.css +++ b/examples/with-next-css/style.css @@ -1,16 +1,7 @@ -.example { - font-size: 50px; - color: papayawhip; -} - -/* Post-CSS */ -/* :root { --some-color: red; } -.example { +body { color: var(--some-color); } - -*/ diff --git a/examples/with-next-less/README.md b/examples/with-next-less/README.md index 49359fed9966be..64831b21505687 100644 --- a/examples/with-next-less/README.md +++ b/examples/with-next-less/README.md @@ -1,15 +1,23 @@ -[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-next-less) - # Example App with next-less +This example demonstrates how to use the [next-less plugin](https://github.com/zeit/next-plugins/tree/master/packages/next-less). + +It includes patterns for with and without CSS Modules, with PostCSS and with additional webpack configurations on top of the next-less plugin. + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-next-less) + ## How to use ### Using `create-next-app` -Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -npx create-next-app --example with-next-less with-next-less-app +npm init next-app --example with-next-less with-next-less-app # or yarn create next-app --example with-next-less with-next-less-app ``` @@ -33,13 +41,4 @@ yarn yarn dev ``` -Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) - -```bash -now -``` - -## The idea behind the example - -This example demonstrates how to use the [next-less plugin](https://github.com/zeit/next-plugins/tree/master/packages/next-less).
    -It includes patterns for with and without CSS Modules, with PostCSS and with additional webpack configurations on top of the next-less plugin. +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-next-less/pages/_document.js b/examples/with-next-less/pages/_document.js index f2482d776129c5..f0ba19e663ca90 100644 --- a/examples/with-next-less/pages/_document.js +++ b/examples/with-next-less/pages/_document.js @@ -7,7 +7,7 @@ You have to include it into the page using either next/head or a custom _documen import Document, { Head, Main, NextScript } from 'next/document' export default class MyDocument extends Document { - render () { + render() { return ( diff --git a/examples/with-next-less/pages/index.js b/examples/with-next-less/pages/index.js index f11bb8c58a3212..cdf852b7c1ab92 100644 --- a/examples/with-next-less/pages/index.js +++ b/examples/with-next-less/pages/index.js @@ -1,6 +1,6 @@ // Without CSS Modules import '../style.less' -export default () =>
    Hello Less!
    +export default () =>
    Hello Less!
    // With CSS Modules /* diff --git a/examples/with-next-offline/README.md b/examples/with-next-offline/README.md new file mode 100644 index 00000000000000..4fa6c12fdc5d52 --- /dev/null +++ b/examples/with-next-offline/README.md @@ -0,0 +1,68 @@ +# next-offline example + +This example demonstrates how to use the [next-offline plugin](https://github.com/hanford/next-offline) It includes manifest.json to install app via chrome + +## How to use + +### Using `create-next-app` + +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npm init next-app --example with-next-offline with-next-offline-app +# or +yarn create next-app --example with-next-offline with-next-offline-app +``` + +### Download + +Download the example: + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-next-offline +cd with-next-offline +``` + +### Install dependecies + +```bash +npm install +# or +yarn +``` + +### Build + +#### Static export + +```bash +npm run export +# or +yarn export +``` + +To serve it yourself, you can run: + +```bash +npx serve -s out +``` + +#### Server hosted + +```bash +npm run build +# or +yarn build +``` + +To serve it yourself, run: + +```bash +npm start +# or +yarn start +``` + +### Deploy + +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-next-offline/next.config.js b/examples/with-next-offline/next.config.js new file mode 100644 index 00000000000000..f714f073f9b6db --- /dev/null +++ b/examples/with-next-offline/next.config.js @@ -0,0 +1,31 @@ +const withOffline = require('next-offline') + +module.exports = withOffline({ + workboxOpts: { + swDest: process.env.NEXT_EXPORT + ? 'service-worker.js' + : 'static/service-worker.js', + runtimeCaching: [ + { + urlPattern: /^https?.*/, + handler: 'NetworkFirst', + options: { + cacheName: 'offlineCache', + expiration: { + maxEntries: 200, + }, + }, + }, + ], + }, + experimental: { + async rewrites() { + return [ + { + source: '/service-worker.js', + destination: '/_next/static/service-worker.js', + }, + ] + }, + }, +}) diff --git a/examples/with-next-offline/package.json b/examples/with-next-offline/package.json new file mode 100644 index 00000000000000..04ed33543b4742 --- /dev/null +++ b/examples/with-next-offline/package.json @@ -0,0 +1,20 @@ +{ + "name": "with-next-offline", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start", + "export": "cross-env NEXT_EXPORT=true next build && cross-env NEXT_EXPORT=true next export" + }, + "dependencies": { + "next": "canary", + "next-offline": "4.0.6", + "react": "16.12.0", + "react-dom": "16.12.0" + }, + "devDependencies": { + "cross-env": "6.0.3" + } +} diff --git a/examples/with-next-offline/pages/_document.js b/examples/with-next-offline/pages/_document.js new file mode 100644 index 00000000000000..13e56914e45537 --- /dev/null +++ b/examples/with-next-offline/pages/_document.js @@ -0,0 +1,24 @@ +import Document, { Html, Head, Main, NextScript } from 'next/document' + +class MyDocument extends Document { + static async getInitialProps(ctx) { + const initialProps = await Document.getInitialProps(ctx) + return { ...initialProps } + } + + render() { + return ( + + + + + +
    + + + + ) + } +} + +export default MyDocument diff --git a/examples/with-next-offline/pages/index.js b/examples/with-next-offline/pages/index.js new file mode 100644 index 00000000000000..ae585c50c7941d --- /dev/null +++ b/examples/with-next-offline/pages/index.js @@ -0,0 +1,3 @@ +export default () => ( +
    Next-Offline Example, try to install app via chrome
    +) diff --git a/examples/with-next-offline/public/manifest.json b/examples/with-next-offline/public/manifest.json new file mode 100644 index 00000000000000..9cdd84306bf5bf --- /dev/null +++ b/examples/with-next-offline/public/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "next-offline", + "short_name": "next-offline", + "description": "Nextjs using service worker via next-offline", + "icons": [ + { + "src": "/static/pwa.png", + "sizes": "192x192", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "start_url": "/", + "display": "standalone" +} diff --git a/examples/with-next-offline/public/static/pwa.png b/examples/with-next-offline/public/static/pwa.png new file mode 100644 index 00000000000000..2b1536f62043f6 Binary files /dev/null and b/examples/with-next-offline/public/static/pwa.png differ diff --git a/examples/with-next-page-transitions/README.md b/examples/with-next-page-transitions/README.md index 38c57167ea5253..0149def9a2c969 100644 --- a/examples/with-next-page-transitions/README.md +++ b/examples/with-next-page-transitions/README.md @@ -1,15 +1,23 @@ -[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-next-page-transitions) - # next-page-transitions example +The [`next-page-transitions`](https://github.com/illinois/next-page-transitions) library is a component that sits at the app level and allows you to animate page changes. It works especially nicely with apps with a shared layout element, like a navbar. This component will ensure that only one page is ever mounted at a time, and manages the timing of animations for you. This component works similarly to [`react-transition-group`](https://github.com/reactjs/react-transition-group) in that it applies classes to a container around your page; it's up to you to write the CSS transitions or animations to make things pretty! + +This example includes two pages with links between them. The "About" page demonstrates how `next-page-transitions` makes it easy to add a loading state when navigating to a page: it will wait for the page to "load" its content (in this examples, that's simulated with a timeout) and then hide the loading indicator and animate in the page when it's done. + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-next-page-transitions) + ## How to use ### Using `create-next-app` -Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -npx create-next-app --example with-next-page-transitions with-next-page-transitions +npm init next-app --example with-next-page-transitions with-next-page-transitions # or yarn create next-app --example with-next-page-transitions with-next-page-transitions ``` @@ -31,15 +39,4 @@ npm run build npm start ``` -Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) - -```bash -now -``` - -## The idea behind the example - -The [`next-page-transitions`](https://github.com/illinois/next-page-transitions) library is a component that sits at the app level and allows you to animate page changes. It works especially nicely with apps with a shared layout element, like a navbar. This component will ensure that only one page is ever mounted at a time, and manages the timing of animations for you. This component works similarly to [`react-transition-group`](https://github.com/reactjs/react-transition-group) in that it applies classes to a container around your page; it's up to you to write the CSS transitions or animations to make things pretty! - -This example includes two pages with links between them. The "About" page demonstrates how `next-page-transitions` makes it easy to add a loading state when navigating to a page: it will wait for the page to "load" its content (in this examples, that's simulated with a timeout) and then hide the loading indicator and animate in the page when it's done. - +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-next-page-transitions/components/Loader.js b/examples/with-next-page-transitions/components/Loader.js index f5f96a72b438a4..200a33f145dd5c 100644 --- a/examples/with-next-page-transitions/components/Loader.js +++ b/examples/with-next-page-transitions/components/Loader.js @@ -1,7 +1,7 @@ import React from 'react' const Loader = () => ( -
    +
    - - ) - } +function MyApp({ Component, pageProps }) { + return ( + <> + } + loadingDelay={500} + loadingTimeout={{ + enter: TIMEOUT, + exit: 0, + }} + loadingClassNames="loading-indicator" + > + + + + + ) } + +export default MyApp diff --git a/examples/with-next-page-transitions/pages/_document.js b/examples/with-next-page-transitions/pages/_document.js index 25e8552099bccb..e6afe8b3bdcf56 100644 --- a/examples/with-next-page-transitions/pages/_document.js +++ b/examples/with-next-page-transitions/pages/_document.js @@ -2,24 +2,24 @@ import React from 'react' import Document, { Head, Main, NextScript } from 'next/document' export default class MyDocument extends Document { - static async getInitialProps (ctx) { + static async getInitialProps(ctx) { const initialProps = await Document.getInitialProps(ctx) return { ...initialProps } } - render () { + render() { return ( - + + +) + +export default Form diff --git a/examples/with-passport/components/header.js b/examples/with-passport/components/header.js new file mode 100644 index 00000000000000..0b605d656739a8 --- /dev/null +++ b/examples/with-passport/components/header.js @@ -0,0 +1,67 @@ +import Link from 'next/link' +import { useUser } from '../lib/hooks' + +const Header = () => { + const user = useUser() + + return ( +
    + + +
    + ) +} + +export default Header diff --git a/examples/with-passport/components/layout.js b/examples/with-passport/components/layout.js new file mode 100644 index 00000000000000..174351ec9b608d --- /dev/null +++ b/examples/with-passport/components/layout.js @@ -0,0 +1,38 @@ +import Head from 'next/head' +import Header from './header' + +const Layout = props => ( + <> + + With Cookies + + +
    + +
    +
    {props.children}
    +
    + + + +) + +export default Layout diff --git a/examples/with-passport/lib/auth-cookies.js b/examples/with-passport/lib/auth-cookies.js new file mode 100644 index 00000000000000..1d215f3a664239 --- /dev/null +++ b/examples/with-passport/lib/auth-cookies.js @@ -0,0 +1,40 @@ +import { serialize, parse } from 'cookie' + +const TOKEN_NAME = 'token' +const MAX_AGE = 60 * 60 * 8 // 8 hours + +export function setTokenCookie(res, token) { + const cookie = serialize(TOKEN_NAME, token, { + maxAge: MAX_AGE, + expires: new Date(Date.now() + MAX_AGE * 1000), + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', + }) + + res.setHeader('Set-Cookie', cookie) +} + +export function removeTokenCookie(res) { + const cookie = serialize(TOKEN_NAME, '', { + maxAge: -1, + path: '/', + }) + + res.setHeader('Set-Cookie', cookie) +} + +export function parseCookies(req) { + // For API Routes we don't need to parse the cookies. + if (req.cookies) return req.cookies + + // For pages we do need to parse the cookies. + const cookie = req.headers?.cookie + return parse(cookie || '') +} + +export function getTokenCookie(req) { + const cookies = parseCookies(req) + return cookies[TOKEN_NAME] +} diff --git a/examples/with-passport/lib/hooks.js b/examples/with-passport/lib/hooks.js new file mode 100644 index 00000000000000..645364466d3c06 --- /dev/null +++ b/examples/with-passport/lib/hooks.js @@ -0,0 +1,31 @@ +import { useEffect } from 'react' +import Router from 'next/router' +import useSWR from 'swr' + +const fetcher = url => + fetch(url) + .then(r => r.json()) + .then(data => { + return { user: data?.user || null } + }) + +export function useUser({ redirectTo, redirectIfFound } = {}) { + const { data, error } = useSWR('/api/user', fetcher) + const user = data?.user + const finished = Boolean(data) + const hasUser = Boolean(user) + + useEffect(() => { + if (!redirectTo || !finished) return + if ( + // If redirectTo is set, redirect if the user was not found. + (redirectTo && !redirectIfFound && !hasUser) || + // If redirectIfFound is also set, redirect if the user was found + (redirectIfFound && hasUser) + ) { + Router.push(redirectTo) + } + }, [redirectTo, redirectIfFound, finished, hasUser]) + + return error ? null : user +} diff --git a/examples/with-passport/lib/iron.js b/examples/with-passport/lib/iron.js new file mode 100644 index 00000000000000..977c4b110dd994 --- /dev/null +++ b/examples/with-passport/lib/iron.js @@ -0,0 +1,14 @@ +import Iron from '@hapi/iron' +import { getTokenCookie } from './auth-cookies' + +// Use an environment variable here instead of a hardcoded value for production +const TOKEN_SECRET = 'this-is-a-secret-value-with-at-least-32-characters' + +export function encryptSession(session) { + return Iron.seal(session, TOKEN_SECRET, Iron.defaults) +} + +export async function getSession(req) { + const token = getTokenCookie(req) + return token && Iron.unseal(token, TOKEN_SECRET, Iron.defaults) +} diff --git a/examples/with-passport/lib/password-local.js b/examples/with-passport/lib/password-local.js new file mode 100644 index 00000000000000..1fc07d7d447813 --- /dev/null +++ b/examples/with-passport/lib/password-local.js @@ -0,0 +1,16 @@ +import Local from 'passport-local' +import { findUser } from './user' + +export const localStrategy = new Local.Strategy(function( + username, + password, + done +) { + findUser({ username, password }) + .then(user => { + done(null, user) + }) + .catch(error => { + done(error) + }) +}) diff --git a/examples/with-passport/lib/user.js b/examples/with-passport/lib/user.js new file mode 100644 index 00000000000000..80276dabd035e9 --- /dev/null +++ b/examples/with-passport/lib/user.js @@ -0,0 +1,27 @@ +// import crypto from 'crypto' + +/** + * User methods. The example doesn't contain a DB, but for real applications you must use a + * db here, such as MongoDB, Fauna, SQL, etc. + */ + +export async function createUser({ username, password }) { + // Here you should create the user and save the salt and hashed password (some dbs may have + // authentication methods that will do it for you so you don't have to worry about it): + // + // const salt = crypto.randomBytes(16).toString('hex') + // const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex') + // const user = await DB.createUser({ username, salt, hash }) + + return { username, createdAt: Date.now() } +} + +export async function findUser({ username, password }) { + // Here you should lookup for the user in your DB and compare the password: + // + // const user = await DB.findUser(...) + // const hash = crypto.pbkdf2Sync(password, user.salt, 1000, 64, 'sha512').toString('hex') + // const passwordsMatch = user.hash === hash + + return { username, createdAt: Date.now() } +} diff --git a/examples/with-passport/package.json b/examples/with-passport/package.json new file mode 100644 index 00000000000000..5c15d1ca55a64c --- /dev/null +++ b/examples/with-passport/package.json @@ -0,0 +1,20 @@ +{ + "name": "with-passport", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@hapi/iron": "6.0.0", + "cookie": "0.4.0", + "express": "4.17.1", + "next": "latest", + "passport": "0.4.1", + "passport-local": "1.0.0", + "react": "latest", + "react-dom": "latest", + "swr": "0.1.16" + }, + "license": "ISC" +} diff --git a/examples/with-passport/pages/api/login.js b/examples/with-passport/pages/api/login.js new file mode 100644 index 00000000000000..62fb8be02f2044 --- /dev/null +++ b/examples/with-passport/pages/api/login.js @@ -0,0 +1,41 @@ +import express from 'express' +import passport from 'passport' +import { localStrategy } from '../../lib/password-local' +import { encryptSession } from '../../lib/iron' +import { setTokenCookie } from '../../lib/auth-cookies' + +const app = express() +const authenticate = (method, req, res) => + new Promise((resolve, reject) => { + passport.authenticate(method, { session: false }, (error, token) => { + if (error) { + reject(error) + } else { + resolve(token) + } + })(req, res) + }) + +app.disable('x-powered-by') + +app.use(passport.initialize()) + +passport.use(localStrategy) + +app.post('/api/login', async (req, res) => { + try { + const user = await authenticate('local', req, res) + // session is the payload to save in the token, it may contain basic info about the user + const session = { ...user } + // The token is a string with the encrypted session + const token = await encryptSession(session) + + setTokenCookie(res, token) + res.status(200).send({ done: true }) + } catch (error) { + console.error(error) + res.status(401).send(error.message) + } +}) + +export default app diff --git a/examples/with-passport/pages/api/logout.js b/examples/with-passport/pages/api/logout.js new file mode 100644 index 00000000000000..1fe3096cdc014e --- /dev/null +++ b/examples/with-passport/pages/api/logout.js @@ -0,0 +1,7 @@ +import { removeTokenCookie } from '../../lib/auth-cookies' + +export default async function logout(req, res) { + removeTokenCookie(res) + res.writeHead(302, { Location: '/' }) + res.end() +} diff --git a/examples/with-passport/pages/api/signup.js b/examples/with-passport/pages/api/signup.js new file mode 100644 index 00000000000000..7972d47838a74b --- /dev/null +++ b/examples/with-passport/pages/api/signup.js @@ -0,0 +1,11 @@ +import { createUser } from '../../lib/user' + +export default async function signup(req, res) { + try { + await createUser(req.body) + res.status(200).send({ done: true }) + } catch (error) { + console.error(error) + res.status(500).end(error.message) + } +} diff --git a/examples/with-passport/pages/api/user.js b/examples/with-passport/pages/api/user.js new file mode 100644 index 00000000000000..dad216c76e9a30 --- /dev/null +++ b/examples/with-passport/pages/api/user.js @@ -0,0 +1,9 @@ +import { getSession } from '../../lib/iron' + +export default async function user(req, res) { + const session = await getSession(req) + // After getting the session you may want to fetch for the user instead + // of sending the session's payload directly, this example doesn't have a DB + // so it won't matter in this case + res.status(200).json({ user: session || null }) +} diff --git a/examples/with-passport/pages/index.js b/examples/with-passport/pages/index.js new file mode 100644 index 00000000000000..0870d849e3d8cd --- /dev/null +++ b/examples/with-passport/pages/index.js @@ -0,0 +1,36 @@ +import { useUser } from '../lib/hooks' +import Layout from '../components/layout' + +const Home = () => { + const user = useUser() + + return ( + +

    Passport.js Example

    + +

    Steps to test the example:

    + +
      +
    1. Click Login and enter an username and password.
    2. +
    3. + You'll be redirected to Home. Click on Profile, notice how your + session is being used through a token stored in a cookie. +
    4. +
    5. + Click Logout and try to go to Profile again. You'll get redirected to + Login. +
    6. +
    + + {user &&

    Currently logged in as: {JSON.stringify(user)}

    } + + +
    + ) +} + +export default Home diff --git a/examples/with-passport/pages/login.js b/examples/with-passport/pages/login.js new file mode 100644 index 00000000000000..64ccd37f435581 --- /dev/null +++ b/examples/with-passport/pages/login.js @@ -0,0 +1,57 @@ +import { useState } from 'react' +import Router from 'next/router' +import { useUser } from '../lib/hooks' +import Layout from '../components/layout' +import Form from '../components/form' + +const Login = () => { + useUser({ redirectTo: '/', redirectIfFound: true }) + + const [errorMsg, setErrorMsg] = useState('') + + async function handleSubmit(e) { + event.preventDefault() + + if (errorMsg) setErrorMsg('') + + const body = { + username: e.currentTarget.username.value, + password: e.currentTarget.password.value, + } + + try { + const res = await fetch('/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + if (res.status === 200) { + Router.push('/') + } else { + throw new Error(await res.text()) + } + } catch (error) { + console.error('An unexpected error happened occurred:', error) + setErrorMsg(error.message) + } + } + + return ( + +
    +
    +
    + +
    + ) +} + +export default Login diff --git a/examples/with-passport/pages/profile.js b/examples/with-passport/pages/profile.js new file mode 100644 index 00000000000000..e864f7480f04b0 --- /dev/null +++ b/examples/with-passport/pages/profile.js @@ -0,0 +1,15 @@ +import { useUser } from '../lib/hooks' +import Layout from '../components/layout' + +const Profile = () => { + const user = useUser({ redirectTo: '/login' }) + + return ( + +

    Profile

    + {user &&

    Your session: {JSON.stringify(user)}

    } +
    + ) +} + +export default Profile diff --git a/examples/with-passport/pages/signup.js b/examples/with-passport/pages/signup.js new file mode 100644 index 00000000000000..a0f31880a8ff6b --- /dev/null +++ b/examples/with-passport/pages/signup.js @@ -0,0 +1,62 @@ +import { useState } from 'react' +import Router from 'next/router' +import { useUser } from '../lib/hooks' +import Layout from '../components/layout' +import Form from '../components/form' + +const Signup = () => { + useUser({ redirectTo: '/', redirectIfFound: true }) + + const [errorMsg, setErrorMsg] = useState('') + + async function handleSubmit(e) { + event.preventDefault() + + if (errorMsg) setErrorMsg('') + + const body = { + username: e.currentTarget.username.value, + password: e.currentTarget.password.value, + } + + if (body.password !== e.currentTarget.rpassword.value) { + setErrorMsg(`The passwords don't match`) + return + } + + try { + const res = await fetch('/api/signup', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + if (res.status === 200) { + Router.push('/login') + } else { + throw new Error(await res.text()) + } + } catch (error) { + console.error('An unexpected error happened occurred:', error) + setErrorMsg(error.message) + } + } + + return ( + +
    + +
    + +
    + ) +} + +export default Signup diff --git a/examples/with-patternfly/README.md b/examples/with-patternfly/README.md new file mode 100644 index 00000000000000..1cc904f8f5d50c --- /dev/null +++ b/examples/with-patternfly/README.md @@ -0,0 +1,42 @@ +# Patternfly example + +This example shows how to use Next.js along with [Patterfly](https://www.patternfly.org/v4/) including handling of external styles and assets. This is intended to show the integration of this design system with the Framework. + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-patternfly) + +## How to use + +### Using `create-next-app` + +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npm init next-app --example with-patternfly with-patternfly-app +# or +yarn create next-app --example with-patternfly with-patternfly-app +``` + +### Download manually + +Download the example: + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-patternfly +cd with-patternfly +``` + +Install it and run: + +```bash +npm install +npm run dev +# or +yarn +yarn dev +``` + +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-patternfly/next.config.js b/examples/with-patternfly/next.config.js new file mode 100644 index 00000000000000..149294b221559e --- /dev/null +++ b/examples/with-patternfly/next.config.js @@ -0,0 +1,107 @@ +const path = require('path') +const withCSS = require('@zeit/next-css') + +const withTM = require('next-transpile-modules')(['@patternfly']) + +const BG_IMAGES_DIRNAME = 'bgimages' + +module.exports = withCSS( + withTM({ + // Webpack config from https://github.com/patternfly/patternfly-react-seed/blob/master/webpack.common.js + webpack(config) { + config.module.rules.push({ + test: /\.(svg|ttf|eot|woff|woff2)$/, + // only process modules with this loader + // if they live under a 'fonts' or 'pficon' directory + include: [ + path.resolve(__dirname, 'node_modules/patternfly/dist/fonts'), + path.resolve( + __dirname, + 'node_modules/@patternfly/react-core/dist/styles/assets/fonts' + ), + path.resolve( + __dirname, + 'node_modules/@patternfly/react-core/dist/styles/assets/pficon' + ), + path.resolve( + __dirname, + 'node_modules/@patternfly/patternfly/assets/fonts' + ), + path.resolve( + __dirname, + 'node_modules/@patternfly/patternfly/assets/pficon' + ), + ], + use: { + loader: 'file-loader', + options: { + // Limit at 50k. larger files emited into separate files + limit: 5000, + publicPath: '/_next/static/fonts/', + outputPath: 'static/fonts/', + name: '[name].[ext]', + }, + }, + }) + + config.module.rules.push({ + test: /\.svg$/, + include: input => input.indexOf('background-filter.svg') > 1, + use: [ + { + loader: 'url-loader', + options: { + limit: 5000, + publicPath: '/_next/static/svgs/', + outputPath: 'static/svgs/', + name: '[name].[ext]', + }, + }, + ], + }) + + config.module.rules.push({ + test: /\.svg$/, + // only process SVG modules with this loader if they live under a 'bgimages' directory + // this is primarily useful when applying a CSS background using an SVG + include: input => input.indexOf(BG_IMAGES_DIRNAME) > -1, + use: { + loader: 'svg-url-loader', + options: {}, + }, + }) + + config.module.rules.push({ + test: /\.svg$/, + // only process SVG modules with this loader when they don't live under a 'bgimages', + // 'fonts', or 'pficon' directory, those are handled with other loaders + include: input => + input.indexOf(BG_IMAGES_DIRNAME) === -1 && + input.indexOf('fonts') === -1 && + input.indexOf('background-filter') === -1 && + input.indexOf('pficon') === -1, + use: { + loader: 'raw-loader', + options: {}, + }, + }) + + config.module.rules.push({ + test: /\.(jpg|jpeg|png|gif)$/i, + use: [ + { + loader: 'url-loader', + options: { + limit: 5000, + publicPath: '/_next/static/images/', + outputPath: 'static/images/', + name: '[name].[ext]', + }, + }, + ], + }) + + return config + }, + }) +) diff --git a/examples/with-patternfly/package.json b/examples/with-patternfly/package.json new file mode 100644 index 00000000000000..1e5f1a89656df4 --- /dev/null +++ b/examples/with-patternfly/package.json @@ -0,0 +1,24 @@ +{ + "name": "with-patternfly", + "author": "Daniel Reinoso ", + "version": "1.0.0", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@patternfly/react-core": "^3.112.3", + "next": "latest", + "next-transpile-modules": "^3.0.2", + "react": "^16.7.0", + "react-dom": "^16.7.0" + }, + "devDependencies": { + "@zeit/next-css": "^1.0.1", + "file-loader": "^3.0.1", + "svg-url-loader": "^3.0.2", + "url-loader": "^1.1.2" + }, + "license": "ISC" +} diff --git a/examples/with-patternfly/pages/_app.js b/examples/with-patternfly/pages/_app.js new file mode 100644 index 00000000000000..4872aa824237e5 --- /dev/null +++ b/examples/with-patternfly/pages/_app.js @@ -0,0 +1,4 @@ +import App from 'next/app' +import '@patternfly/react-core/dist/styles/base.css' + +export default App diff --git a/examples/with-patternfly/pages/index.js b/examples/with-patternfly/pages/index.js new file mode 100644 index 00000000000000..374ff2e0d7ecf1 --- /dev/null +++ b/examples/with-patternfly/pages/index.js @@ -0,0 +1,34 @@ +import { useState } from 'react' +import { Button, Wizard } from '@patternfly/react-core' + +const steps = [ + { name: 'Step 1', component:

    Step 1

    }, + { name: 'Step 2', component:

    Step 2

    }, + { name: 'Step 3', component:

    Step 3

    }, + { name: 'Step 4', component:

    Step 4

    }, + { name: 'Review', component:

    Review Step

    , nextButtonText: 'Finish' }, +] + +export default () => { + const [isOpen, setIsOpen] = useState(false) + return ( + <> + + {isOpen && ( + setIsOpen(false)} + title="Simple Wizard" + description="Simple Wizard Description" + steps={steps} + /> + )} + + ) +} diff --git a/examples/with-pkg/README.md b/examples/with-pkg/README.md deleted file mode 100644 index f8fb85eaa6a2b2..00000000000000 --- a/examples/with-pkg/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Example with pkg - -## How to use - -### Using `create-next-app` - -Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: - -```bash -npx create-next-app --example with-pkg with-pkg-app -# or -yarn create next-app --example with-pkg with-pkg-app -``` - -### Download manually - -Download the example: - -```bash -curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-pkg -cd with-pkg -``` - -Install it and run pkg: - -```bash -npm install -yarn run build -yarn run dist -``` - -Execute the binary file: - -```bash -PORT=4000 ./dist/with-pkg-macos -``` - -Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) - -```bash -now -``` - -## The idea behind the example - -This example demonstrate how you can use [pkg](https://github.com/zeit/pkg) to create a binary version of a Next.js application. - -To do it we need to create at least a super simple custom server that allow us to run `node server.js` instead of `next` or `next start`. We also need to create a `index.js` that works as the entry point for **pkg**, in that file we force to set NODE_ENV as production. diff --git a/examples/with-pkg/index.js b/examples/with-pkg/index.js deleted file mode 100644 index 0306f3c5e75b81..00000000000000 --- a/examples/with-pkg/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// when pkg-ing the app we set NODE_ENV as production and require the `./server.js` -process.env.NODE_ENV = 'production' -require('./server.js') diff --git a/examples/with-pkg/package.json b/examples/with-pkg/package.json deleted file mode 100644 index 709c0fcb52970e..00000000000000 --- a/examples/with-pkg/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "with-pkg", - "version": "1.0.0", - "bin": "index.js", - "author": "Sergio Daniel Xalambrí ", - "license": "MIT", - "scripts": { - "dev": "node server.js", - "build": "next build", - "prestart": "npm run build", - "start": "NODE_ENV=production node server.js", - "predist": "npm run build", - "dist": "pkg . --out-dir dist" - }, - "dependencies": { - "next": "latest", - "react": "^16.7.0", - "react-dom": "^16.7.0" - }, - "devDependencies": { - "pkg": "^4.2.2" - }, - "pkg": { - "assets": [ - ".next/**/*" - ], - "scripts": [ - ".next/server/**/*.js" - ] - } -} diff --git a/examples/with-pkg/pages/index.js b/examples/with-pkg/pages/index.js deleted file mode 100644 index 149ab5a12a5b3f..00000000000000 --- a/examples/with-pkg/pages/index.js +++ /dev/null @@ -1 +0,0 @@ -export default () =>

    Home page

    diff --git a/examples/with-pkg/server.js b/examples/with-pkg/server.js deleted file mode 100644 index 3cf974a882efc3..00000000000000 --- a/examples/with-pkg/server.js +++ /dev/null @@ -1,17 +0,0 @@ -const { createServer } = require('http') -const { parse } = require('url') -const next = require('next') - -const port = parseInt(process.env.PORT, 10) || 3000 -const dev = process.env.NODE_ENV !== 'production' -const app = next({ dev, dir: __dirname }) -const handle = app.getRequestHandler() - -app.prepare().then(() => { - createServer((req, res) => - handle(req, res, parse(req.url, true).pathname) - ).listen(port, err => { - if (err) throw err - console.log(`> Ready on http://localhost:${port}`) - }) -}) diff --git a/examples/with-polyfills/README.md b/examples/with-polyfills/README.md index 0493ac7116cf9c..a92e32e4d8204e 100644 --- a/examples/with-polyfills/README.md +++ b/examples/with-polyfills/README.md @@ -1,15 +1,23 @@ -[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-polyfills) - # Example app with polyfills +Next.js supports browsers from IE10 to the latest. It adds polyfills as they need. But Next.js cannot add polyfills for code inside NPM modules. So sometimes, you need to add polyfills by yourself. + +This how you can do it easily with Next.js's custom webpack config feature. + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-polyfills) + ## How to use ### Using `create-next-app` -Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -npx create-next-app --example with-polyfills with-polyfills-app +npm init next-app --example with-polyfills with-polyfills-app # or yarn create next-app --example with-polyfills with-polyfills-app ``` @@ -33,15 +41,4 @@ yarn yarn dev ``` -Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) - -```bash -now -``` - -## The idea behind the example - -Next.js supports browsers from IE10 to the latest. It adds polyfills as they need. But Next.js cannot add polyfills for code inside NPM modules. -So sometimes, you need to add polyfills by yourself. - -This how you can do it easily with Next.js's custom webpack config feature. +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-polyfills/client/polyfills.js b/examples/with-polyfills/client/polyfills.js index 9ac9779f40f8cb..044c019ac47d57 100644 --- a/examples/with-polyfills/client/polyfills.js +++ b/examples/with-polyfills/client/polyfills.js @@ -1,13 +1,8 @@ /* eslint no-extend-native: 0 */ -// core-js comes with Next.js. So, you can import it like below -import includes from 'core-js/library/fn/string/virtual/includes' -import repeat from 'core-js/library/fn/string/virtual/repeat' -import assign from 'core-js/library/fn/object/assign' -// Add your polyfills -// This files runs at the very beginning (even before React and Next.js core) +// This file runs before React and Next.js core +// This file is loaded for all browsers +// Next.js includes a number of polyfills only for older browsers like IE11 +// Make sure you don't duplicate these in this file +// https://github.com/zeit/next.js/blob/canary/packages/next-polyfill-nomodule/src/index.js console.log('Load your polyfills') - -String.prototype.includes = includes -String.prototype.repeat = repeat -Object.assign = assign diff --git a/examples/with-polyfills/next.config.js b/examples/with-polyfills/next.config.js index 65394c60e2e1a3..3c845eee6cb3c5 100644 --- a/examples/with-polyfills/next.config.js +++ b/examples/with-polyfills/next.config.js @@ -1,5 +1,5 @@ module.exports = { - webpack: function (cfg) { + webpack: function(cfg) { const originalEntry = cfg.entry cfg.entry = async () => { const entries = await originalEntry() @@ -15,5 +15,5 @@ module.exports = { } return cfg - } + }, } diff --git a/examples/with-portals-ssr/README.md b/examples/with-portals-ssr/README.md index 1a22ba15d11754..d83111ec9e5609 100644 --- a/examples/with-portals-ssr/README.md +++ b/examples/with-portals-ssr/README.md @@ -1,13 +1,21 @@ # Example with Server Side Rendered portals +An example of Server Side Rendered React [Portals](https://reactjs.org/docs/portals.html) with [`@jesstelford/react-portal-universal`](https://www.npmjs.com/package/@jesstelford/react-portal-universal) and Next.js. + +## Deploy your own + +Deploy the example using [ZEIT Now](https://zeit.co/now): + +[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-portals-ssr) + ## How to use ### Using `create-next-app` -Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example: +Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -npx create-next-app --example with-portals-ssr with-portals-ssr +npm init next-app --example with-portals-ssr with-portals-ssr # or yarn create next-app --example with-portals-ssr with-portals-ssr ``` @@ -31,14 +39,4 @@ yarn yarn dev ``` -Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) - -```bash -now -``` - -## The idea behind the example - -An example of Server Side Rendered React [Portals](https://reactjs.org/docs/portals.html) with -[`@jesstelford/react-portal-universal`](https://www.npmjs.com/package/@jesstelford/react-portal-universal) -and Next.js. +Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/with-portals-ssr/pages/_app.js b/examples/with-portals-ssr/pages/_app.js index 807b18a81dd22d..06998cb0e3edc4 100644 --- a/examples/with-portals-ssr/pages/_app.js +++ b/examples/with-portals-ssr/pages/_app.js @@ -1,8 +1,8 @@ import React from 'react' -import App, { Container } from 'next/app' +import App from 'next/app' import { prepareClientPortals } from '@jesstelford/react-portal-universal' -if (process.browser) { +if (typeof window !== 'undefined') { // On the client, we have to run this once before React attempts a render. // Here in _app is a great place to do it as this file is only required once, // and right now (outside the constructor) is before React is invoked. @@ -10,14 +10,14 @@ if (process.browser) { } class MyApp extends App { - render () { + render() { const { Component, pageProps } = this.props return ( - - {/* This is where we'll render one of our universal portals */} -