Compare commits
24 Commits
v2.0.0-alp
...
forking
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
847b41f2be | ||
|
|
3c68f600c1 | ||
|
|
6d225f7b10 | ||
|
|
83fd77f1f2 | ||
|
|
59fe8e0c7d | ||
|
|
8967281015 | ||
|
|
00490e17fb | ||
|
|
b9a2133bb3 | ||
|
|
5ae016dc3f | ||
|
|
9dbb6db245 | ||
|
|
c36cbbcbb3 | ||
|
|
7ecb67b4df | ||
|
|
04ebd34223 | ||
|
|
cc68f99f48 | ||
|
|
08d74f098c | ||
|
|
1ff8a1dd36 | ||
|
|
fc8d697d81 | ||
|
|
1d2f2acdc8 | ||
|
|
fdd93c43cd | ||
|
|
0cf81d05d7 | ||
|
|
68307b7159 | ||
|
|
4b12379a2b | ||
|
|
6e247a4e69 | ||
|
|
d6bf079c87 |
@@ -1,4 +1,3 @@
|
||||
lib
|
||||
node_modules
|
||||
reports
|
||||
*.log
|
||||
@@ -26,6 +25,27 @@ eslint.config.*
|
||||
/scripts
|
||||
/types
|
||||
/recipes/*/templates
|
||||
packages/generator/templates
|
||||
/packages/generator/templates
|
||||
/packages/cli/lib
|
||||
|
||||
/nextjs
|
||||
|
||||
|
||||
// COPIED FROM nextjs/.eslintignore
|
||||
/nextjs/**/.next/**
|
||||
/nextjs/**/_next/**
|
||||
/nextjs/**/dist/**
|
||||
/nextjs/examples/with-typescript-eslint-jest/**
|
||||
/nextjs/examples/with-kea/**
|
||||
/nextjs/packages/next/bundles/webpack/packages/*.runtime.js
|
||||
/nextjs/packages/next/compiled/**/*
|
||||
/nextjs/packages/react-refresh-utils/**/*.js
|
||||
/nextjs/packages/react-dev-overlay/lib/**
|
||||
/nextjs/**/__tmp__/**
|
||||
/nextjs/.github/actions/next-stats-action/.work
|
||||
/nextjs/packages/next-codemod/transforms/__testfixtures__/**/*
|
||||
/nextjs/packages/next-codemod/transforms/__tests__/**/*
|
||||
/nextjs/packages/next-codemod/**/*.js
|
||||
/nextjs/packages/next-codemod/**/*.d.ts
|
||||
/nextjs/packages/next-env/**/*.d.ts
|
||||
/nextjs/test/integration/async-modules/**
|
||||
/nextjs/test-timings.json
|
||||
|
||||
5
.github/workflows/main.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: Blitz
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -52,14 +52,13 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
node_version: [12, 15]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
node-version: "15"
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
270
.github/workflows/nextjs.yml
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
name: Nextjs
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [canary]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./
|
||||
env:
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
outputs:
|
||||
docsChange: ${{ steps.docs-change.outputs.DOCS_CHANGE }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 25
|
||||
|
||||
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- run: yarn workspace @blitzjs/next prepublish
|
||||
- run: node run-tests.js --timings --write-timings -g 1/1
|
||||
working-directory: nextjs
|
||||
- name: Check docs only change
|
||||
working-directory: nextjs
|
||||
run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change')
|
||||
id: docs-change
|
||||
- run: echo ${{steps.docs-change.outputs.DOCS_CHANGE}}
|
||||
- uses: actions/cache@v2
|
||||
id: cache-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}
|
||||
|
||||
checkPrecompiled:
|
||||
name: Check Pre-compiled
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
env:
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}
|
||||
- run: ./check-pre-compiled.sh
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
|
||||
testUnit:
|
||||
name: Test Unit
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
env:
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}
|
||||
|
||||
- run: node run-tests.js --timings --type unit -g 1/1
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
|
||||
testIntegration:
|
||||
name: Test Integration
|
||||
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:
|
||||
- run: echo ${{needs.build.outputs.docsChange}}
|
||||
working-directory: ./
|
||||
- uses: actions/cache@v2
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
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
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
|
||||
- run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
|
||||
testElectron:
|
||||
name: Test Electron
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
env:
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
TEST_ELECTRON: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
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
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
|
||||
- run: yarn add -W --dev spectron@7.0.0 electron@5.0.0
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
working-directory: ./
|
||||
|
||||
- run: xvfb-run node run-tests.js test/integration/with-electron/test/index.test.js
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
|
||||
testsPass:
|
||||
name: thank you, next
|
||||
runs-on: ubuntu-latest
|
||||
needs: [checkPrecompiled, testIntegration, testUnit]
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
testFutureDependencies:
|
||||
name: Webpack 5 (Basic, Production, Acceptance)
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./
|
||||
env:
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
NEXT_PRIVATE_TEST_WEBPACK5_MODE: 1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 25
|
||||
|
||||
- run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change')
|
||||
id: docs-change
|
||||
|
||||
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
- run: yarn install --check-files
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
- run: xvfb-run node run-tests.js test/integration/{fallback-modules,link-ref,production,basic,async-modules,font-optimization,ssr-ctx}/test/index.test.js test/acceptance/*.test.js
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
working-directory: nextjs
|
||||
|
||||
testLegacyReact:
|
||||
name: React 16 + Webpack 4 (Basic, Production, Acceptance)
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./
|
||||
env:
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 25
|
||||
|
||||
- run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change')
|
||||
id: docs-change
|
||||
|
||||
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
- run: cat package.json | jq '.resolutions.react = "^16.14.0"' > package.json.tmp && mv package.json.tmp package.json
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
- run: cat package.json | jq '.resolutions."react-dom" = "^16.14.0"' > package.json.tmp && mv package.json.tmp package.json
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
- run: yarn install --check-files
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
- run: yarn list react react-dom
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
- run: xvfb-run node run-tests.js test/integration/{link-ref,production,basic,async-modules,font-optimization,ssr-ctx,worker-loader}/test/index.test.js test/acceptance/*.test.js
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
working-directory: nextjs
|
||||
|
||||
testFirefox:
|
||||
name: Test Firefox (production)
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
env:
|
||||
HEADLESS: true
|
||||
BROWSERNAME: "firefox"
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}
|
||||
- run: node run-tests.js test/integration/production/test/index.test.js
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
|
||||
testSafari:
|
||||
name: Test Safari (production)
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
env:
|
||||
BROWSERSTACK: true
|
||||
BROWSERNAME: "safari"
|
||||
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@v2
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}
|
||||
- run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production/test/index.test.js'
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
|
||||
testSafariOld:
|
||||
name: Test Safari 10.1 (nav)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build, testSafari]
|
||||
env:
|
||||
BROWSERSTACK: true
|
||||
LEGACY_SAFARI: true
|
||||
BROWSERNAME: "safari"
|
||||
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@v2
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}
|
||||
- run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production-nav/test/index.test.js'
|
||||
if: ${{needs.build.outputs.docsChange != 'docs only change'}}
|
||||
1
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
.jest-*
|
||||
lib
|
||||
node_modules
|
||||
reports
|
||||
*.log
|
||||
|
||||
@@ -19,3 +19,4 @@ bin
|
||||
!packages/blitz/src/bin
|
||||
packages/generator/templates/**
|
||||
.github/ISSUE_TEMPLATE/bug_report.md
|
||||
nextjs/.github/workflows/**
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"devDependencies": {
|
||||
"@cypress/skip-test": "2.6.0",
|
||||
"@next/bundle-analyzer": "^10.0.6",
|
||||
"@testing-library/react": "^11.2.3",
|
||||
"@testing-library/react": "11.2.5",
|
||||
"@testing-library/react-hooks": "^4.0.1",
|
||||
"@types/passport-auth0": "1.0.4",
|
||||
"@types/passport-github2": "1.2.4",
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/skip-test": "2.6.0",
|
||||
"@testing-library/react": "^11.2.3",
|
||||
"@testing-library/react": "11.2.5",
|
||||
"@testing-library/react-hooks": "^4.0.1",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/secure-password": "3.1.0",
|
||||
|
||||
@@ -40,14 +40,10 @@
|
||||
"zod": "1.11.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "5.11.9",
|
||||
"@testing-library/react": "^11.2.3",
|
||||
"@testing-library/react": "11.2.5",
|
||||
"@testing-library/react-hooks": "^4.0.1",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/secure-password": "3.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "~4.14.0",
|
||||
"@typescript-eslint/parser": "~4.14.0",
|
||||
"babel-eslint": "~10.1.0",
|
||||
"eslint": "7.21.0",
|
||||
"eslint-config-react-app": "~6.0.0",
|
||||
@@ -58,13 +54,6 @@
|
||||
"eslint-plugin-react-hooks": "~4.2.0",
|
||||
"husky": "5.1.2",
|
||||
"jest": "^26.6.3",
|
||||
"jest-environment-jsdom-fourteen": "^1.0.1",
|
||||
"jest-watch-typeahead": "^0.6.1",
|
||||
"lint-staged": "10.5.4",
|
||||
"prettier": "2.2.1",
|
||||
"pretty-quick": "3.1.0",
|
||||
"start-server-and-test": "1.11.7",
|
||||
"ts-jest": "26.5.0",
|
||||
"typescript": "4.1.5"
|
||||
},
|
||||
"private": true
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "17.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "~4.14.0",
|
||||
"@typescript-eslint/parser": "~4.14.0",
|
||||
"babel-eslint": "~10.1.0",
|
||||
"eslint": "7.21.0",
|
||||
"eslint-config-react-app": "~6.0.0",
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
"react-dom": "0.0.0-experimental-3310209d0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "~4.14.0",
|
||||
"@typescript-eslint/parser": "~4.14.0",
|
||||
"babel-eslint": "~10.1.0",
|
||||
"eslint": "7.21.0",
|
||||
"eslint-config-react-app": "~6.0.0",
|
||||
|
||||
6
nextjs/.github/CODEOWNERS
vendored
@@ -1,6 +1,6 @@
|
||||
# Learn how to add code owners here:
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @timneutkens @Timer @ijjk @lfades
|
||||
/docs/ @timneutkens @Timer @ijjk @lfades @chibicode
|
||||
/examples/ @timneutkens @Timer @ijjk @lfades @chibicode
|
||||
* @timneutkens @Timer @ijjk @lfades @divmain
|
||||
/docs/ @timneutkens @Timer @ijjk @lfades @divmain @leerob
|
||||
/examples/ @timneutkens @Timer @ijjk @lfades @divmain @leerob
|
||||
|
||||
24
nextjs/.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
@@ -3,65 +3,73 @@ about: Create a bug report for the Next.js core
|
||||
title: ''
|
||||
labels: 'template: bug'
|
||||
issue_body: true
|
||||
inputs:
|
||||
- type: description
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible.
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section.
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: 'Please first verify if your issue exists in the Next.js canary release line: `npm install next@canary`.'
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: 'next@canary is the beta version of Next.js. It includes all features and fixes that are pending to land on the stable release line.'
|
||||
- type: input
|
||||
attributes:
|
||||
label: What version of Next.js are you using?
|
||||
description: 'For example: 10.0.1'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: What version of Node.js are you using?
|
||||
description: 'For example: 12.0.0'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: What browser are you using?
|
||||
description: 'For example: Chrome, Safari'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: What operating system are you using?
|
||||
description: 'For example: macOS, Windows'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: How are you deploying your application?
|
||||
description: 'For example: next start, next export, Vercel, Other platform'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the Bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce the behavior, please provide a clear code snippets that always reproduces the issue or a GitHub repository. Screenshots can be provided in the issue body below.
|
||||
validations:
|
||||
required: true
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear.
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Contributors should be able to follow the steps provided in order to reproduce the bug.
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: These steps are used to add integration tests to ensure the same issue does not happen again. Thanks in advance!
|
||||
|
||||
@@ -3,64 +3,73 @@ about: Create a bug report for the examples
|
||||
title: ''
|
||||
labels: 'type: example,template: bug'
|
||||
issue_body: true
|
||||
inputs:
|
||||
- type: description
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for taking the time to file a examples bug report! Please fill out this form as completely as possible.
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section.
|
||||
- type: input
|
||||
attributes:
|
||||
label: What example does this report relate to?
|
||||
description: 'For example: with-styled-components'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: What version of Next.js are you using?
|
||||
description: 'For example: 10.0.1'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: What version of Node.js are you using?
|
||||
description: 'For example: 12.0.0'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: What browser are you using?
|
||||
description: 'For example: Chrome, Safari'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: What operating system are you using?
|
||||
description: 'For example: macOS, Windows'
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: How are you deploying your application?
|
||||
description: 'For example: next start, next export, Vercel, Other platform'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the Bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce the behavior, please provide a clear code snippets that always reproduces the issue or a GitHub repository. Screenshots can be provided in the issue body below.
|
||||
validations:
|
||||
required: true
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear.
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Contributors should be able to follow the steps provided in order to reproduce the bug.
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks in advance!
|
||||
|
||||
@@ -3,25 +3,28 @@ about: Create a feature request for the Next.js core
|
||||
title: ''
|
||||
labels: 'template: story'
|
||||
issue_body: true
|
||||
inputs:
|
||||
- type: description
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thanks for taking the time to file a feature request! Please fill out this form as completely as possible.
|
||||
- type: description
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: 'Feature requests will be converted to the GitHub Discussions "Ideas" section.'
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the feature you'd like to request
|
||||
description: A clear and concise description of what you want and what your use case is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
- run: yarn install --check-files
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
- run: xvfb-run node run-tests.js test/integration/{link-ref,production,basic,async-modules,font-optimization,ssr-ctx}/test/index.test.js test/acceptance/*.test.js
|
||||
- run: xvfb-run node run-tests.js test/integration/{fallback-modules,link-ref,production,basic,async-modules,font-optimization,ssr-ctx}/test/index.test.js test/acceptance/*.test.js
|
||||
if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }}
|
||||
|
||||
testLegacyReact:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[subrepo]
|
||||
remote = git@github.com:blitz-js/next.js.git
|
||||
branch = canary
|
||||
commit = 5a4b3061adf94b802f4d65f99ba1cc80d7867cd9
|
||||
parent = c699d4f021de0cbe7d8e6619e46078c0b31a1ada
|
||||
commit = 9808a42f5358d5c106bc8f57e04607d78b9da2a2
|
||||
parent = 83fd77f1f2794d3423839f59f7c57b4da019f196
|
||||
method = merge
|
||||
cmdver = 0.4.3
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// Disable automatic instrumentation
|
||||
process.env.OTEL_NO_PATCH_MODULES = '*'
|
||||
|
||||
const { NodeTracerProvider } = require('@opentelemetry/node')
|
||||
const { SimpleSpanProcessor } = require('@opentelemetry/tracing')
|
||||
const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin')
|
||||
|
||||
const tracerProvider = new NodeTracerProvider({
|
||||
// All automatic instrumentation plugins have to be disabled as it affects worker_thread/child_process bootup performance
|
||||
plugins: {
|
||||
mongodb: { enabled: false, path: '@opentelemetry/plugin-mongodb' },
|
||||
grpc: { enabled: false, path: '@opentelemetry/plugin-grpc' },
|
||||
'@grpc/grpc-js': { enabled: false, path: '@opentelemetry/plugin-grpc-js' },
|
||||
http: { enabled: false, path: '@opentelemetry/plugin-http' },
|
||||
https: { enabled: false, path: '@opentelemetry/plugin-https' },
|
||||
mysql: { enabled: false, path: '@opentelemetry/plugin-mysql' },
|
||||
pg: { enabled: false, path: '@opentelemetry/plugin-pg' },
|
||||
redis: { enabled: false, path: '@opentelemetry/plugin-redis' },
|
||||
ioredis: { enabled: false, path: '@opentelemetry/plugin-ioredis' },
|
||||
'pg-pool': { enabled: false, path: '@opentelemetry/plugin-pg-pool' },
|
||||
express: { enabled: false, path: '@opentelemetry/plugin-express' },
|
||||
'@hapi/hapi': {
|
||||
enabled: false,
|
||||
path: '@opentelemetry/hapi-instrumentation',
|
||||
},
|
||||
koa: { enabled: false, path: '@opentelemetry/koa-instrumentation' },
|
||||
dns: { enabled: false, path: '@opentelemetry/plugin-dns' },
|
||||
},
|
||||
})
|
||||
|
||||
tracerProvider.addSpanProcessor(
|
||||
new SimpleSpanProcessor(
|
||||
new ZipkinExporter({
|
||||
serviceName: 'next-js',
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
tracerProvider.register()
|
||||
@@ -21,11 +21,26 @@ export default function Custom404() {
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: You can use [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) inside this page if you need to fetch data at build time.
|
||||
|
||||
## 500 Page
|
||||
|
||||
By default Next.js provides a 500 error page that matches the default 404 page’s style. This page is not statically optimized as it allows server-side errors to be reported. This is why 404 and 500 (other errors) are separated.
|
||||
Server-rendering an error page for every visit adds complexity to responding to errors. To help users get responses to errors as fast as possible, Next.js provides a static 500 page by default without having to add any additional files.
|
||||
|
||||
### Customizing The Error Page
|
||||
### Customizing The 500 Page
|
||||
|
||||
To customize the 500 page you can create a `pages/500.js` file. This file is statically generated at build time.
|
||||
|
||||
```jsx
|
||||
// pages/500.js
|
||||
export default function Custom500() {
|
||||
return <h1>500 - Server-side error occurred</h1>
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: You can use [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) inside this page if you need to fetch data at build time.
|
||||
|
||||
### More Advanced Error Page Customizing
|
||||
|
||||
500 errors are handled both client-side and server-side by the `Error` component. If you wish to override it, define the file `pages/_error.js` and add the following code:
|
||||
|
||||
|
||||
@@ -217,9 +217,9 @@ export default function IndexPage(props) {
|
||||
|
||||
## Leveraging the NEXT_LOCALE cookie
|
||||
|
||||
Next.js supports overriding the accept-language header with a `NEXT_LOCALE=the-locale` cookie. This cookie can be set using a language switcher and then when a user comes back to the site it will leverage the locale specified in the cookie.
|
||||
Next.js supports overriding the accept-language header with a `NEXT_LOCALE=the-locale` cookie. This cookie can be set using a language switcher and then when a user comes back to the site it will leverage the locale specified in the cookie when redirecting from `/` to the correct locale location.
|
||||
|
||||
For example, if a user prefers the locale `fr` but a `NEXT_LOCALE=en` cookie is set the `en` locale will be used instead until the cookie is removed or expired.
|
||||
For example, if a user prefers the locale `fr` in their accept-language header but a `NEXT_LOCALE=en` cookie is set the `en` locale when visiting `/` the user will be redirected to the `en` locale location until the cookie is removed or expired.
|
||||
|
||||
## Search Engine Optimization
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ Next.js automatically supports the `tsconfig.json` and `jsconfig.json` `"paths"`
|
||||
|
||||
> Note: `jsconfig.json` can be used when you don't use TypeScript
|
||||
|
||||
> Note: you need to restart dev server to reflect modifications done in `tsconfig.json` / `jsconfig.json`
|
||||
|
||||
These options allow you to configure module aliases, for example a common pattern is aliasing certain directories to use absolute paths.
|
||||
|
||||
One useful feature of these options is that they integrate automatically into certain editors, for example vscode.
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Enables browser source map generation during the production build.
|
||||
|
||||
# Source Maps
|
||||
|
||||
Source Maps are enabled by default during development. During production builds they are disabled as generation source maps can significantly increase build times and memory usage while being generated.
|
||||
Source Maps are enabled by default during development. During production builds, they are disabled as generating source maps can significantly increase build times and memory usage while being generated.
|
||||
|
||||
Next.js provides a configuration flag you can use to enable browser source map generation during the production build:
|
||||
|
||||
@@ -15,9 +15,9 @@ module.exports = {
|
||||
}
|
||||
```
|
||||
|
||||
When the `productionBrowserSourceMaps` option is enabled the source maps will be output in the same directory as the JavaScript files, Next.js will automatically serve these files when requested.
|
||||
When the `productionBrowserSourceMaps` option is enabled, the source maps will be output in the same directory as the JavaScript files. Next.js will automatically serve these files when requested.
|
||||
|
||||
## Caveats
|
||||
|
||||
- Can increase `next build` time
|
||||
- Adding source maps can increase `next build` time
|
||||
- Increases memory usage during `next build`
|
||||
|
||||
@@ -227,6 +227,17 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// this gets converted to /(en|fr|de)/(.*) so will not match the top-level
|
||||
// `/` or `/fr` routes like /:path* would
|
||||
source: '/(.*)',
|
||||
headers: [
|
||||
{
|
||||
key: 'x-hello',
|
||||
value: 'worlld',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
@@ -152,9 +152,21 @@ module.exports = {
|
||||
locale: false,
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
// this gets converted to /(en|fr|de)/(.*) so will not match the top-level
|
||||
// `/` or `/fr` routes like /:path* would
|
||||
source: '/(.*)',
|
||||
destination: '/another',
|
||||
permanent: false,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
In some rare cases, you might need to assign a custom status code for older HTTP Clients to properly redirect. In these cases, you can use the `statusCode` property instead of the `permanent` property, but not both. Note: to ensure IE11 compatibility a `Refresh` header is automatically added for the 308 status code.
|
||||
|
||||
## Other Redirects
|
||||
|
||||
- Inside [API Routes](/docs/api-routes/response-helpers.md), you can use `res.redirect()`.
|
||||
- Inside [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) and [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering), you can redirect specific pages at request-time.
|
||||
|
||||
@@ -244,6 +244,12 @@ module.exports = {
|
||||
destination: '/en/another',
|
||||
locale: false,
|
||||
},
|
||||
{
|
||||
// this gets converted to /(en|fr|de)/(.*) so will not match the top-level
|
||||
// `/` or `/fr` routes like /:path* would
|
||||
source: '/(.*)',
|
||||
destination: '/another',
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ The page above is an AMP-only page, which means:
|
||||
|
||||
- The page has no Next.js or React client-side runtime
|
||||
- The page is automatically optimized with [AMP Optimizer](https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer), an optimizer that applies the same transformations as AMP caches (improves performance by up to 42%)
|
||||
- The page has an user-accessible (optimized) version of the page and a search-engine indexable (unoptimized) version of the page
|
||||
- The page has a user-accessible (optimized) version of the page and a search-engine indexable (unoptimized) version of the page
|
||||
|
||||
## Hybrid AMP Page
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ The following is the definition of the `router` object returned by both [`useRou
|
||||
- `locales`: `String[]` - All supported locales (if enabled).
|
||||
- `defaultLocale`: `String` - The current default locale (if enabled).
|
||||
- `isReady`: `boolean` - Whether the router fields are updated client-side and ready for use. Should only be used inside of `useEffect` methods and not for conditionally rendering on the server.
|
||||
- `isPreview`: `boolean` - Whether the application is currently in [preview mode](/docs/advanced-features/preview-mode.md).
|
||||
|
||||
Additionally, the following methods are also included inside `router`:
|
||||
|
||||
@@ -71,7 +72,9 @@ router.push(url, as, options)
|
||||
- `url` - The URL to navigate to
|
||||
- `as` - Optional decorator for the URL that will be shown in the browser. Before Next.js 9.5.3 this was used for dynamic routes, check our [previous docs](https://nextjs.org/docs/tag/v9.5.2/api-reference/next/link#dynamic-routes) to see how it worked
|
||||
- `options` - Optional object with the following configuration options:
|
||||
- `scroll`: Scroll to the top of the page after a navigation. Defaults to `true`
|
||||
- [`shallow`](/docs/routing/shallow-routing.md): Update the path of the current page without rerunning [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation), [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering) or [`getInitialProps`](/docs/api-reference/data-fetching/getInitialProps.md). Defaults to `false`
|
||||
- `scroll` - Optional boolean, controls scrolling to the top of the page after navigation. Defaults to `true`
|
||||
|
||||
> You don't need to use `router.push` for external URLs. [window.location](https://developer.mozilla.org/en-US/docs/Web/API/Window/location) is better suited for those cases.
|
||||
|
||||
|
||||
@@ -17,10 +17,7 @@ For example, the API route `pages/api/post/[pid].js` has the following code:
|
||||
|
||||
```js
|
||||
export default function handler(req, res) {
|
||||
const {
|
||||
query: { pid },
|
||||
} = req
|
||||
|
||||
const { pid } = req.query
|
||||
res.end(`Post: ${pid}`)
|
||||
}
|
||||
```
|
||||
@@ -69,10 +66,7 @@ An API route for `pages/api/post/[...slug].js` could look like this:
|
||||
|
||||
```js
|
||||
export default function handler(req, res) {
|
||||
const {
|
||||
query: { slug },
|
||||
} = req
|
||||
|
||||
const { slug } = req.query
|
||||
res.end(`Post: ${slug.join(', ')}`)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ description: Next.js supports API Routes, which allow you to build your API with
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
API routes provide a straightforward solution to build your **API** with Next.js.
|
||||
API routes provide a solution to build your **API** with Next.js.
|
||||
|
||||
Any file inside the folder `pages/api` is mapped to `/api/*` and will be treated as an API endpoint instead of a `page`. They are server-side only bundles and won't increase your client-side bundle size.
|
||||
|
||||
@@ -46,6 +46,13 @@ export default function handler(req, res) {
|
||||
|
||||
To fetch API endpoints, take a look into any of the examples at the start of this section.
|
||||
|
||||
## Use Cases
|
||||
|
||||
For new projects, you can build your entire API with API Routes. If you have an existing API, you do not need to forward calls to the API through an API Route. Some other use cases for API Routes are:
|
||||
|
||||
- Masking the URL of an external service (e.g. `/api/secret` instead of `https://company.com/secret-url`)
|
||||
- Using [Environment Variables](/docs/basic-features/environment-variables.md) on the server to securely access external services.
|
||||
|
||||
## Caveats
|
||||
|
||||
- API Routes [do not specify CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), meaning they are **same-origin only** by default. You can customize such behavior by wrapping the request handler with the [cors middleware](/docs/api-routes/api-middlewares.md#connectexpress-middleware-support).
|
||||
|
||||
@@ -26,3 +26,5 @@ The included helpers are:
|
||||
- `res.json(json)` - Sends a JSON response. `json` must be a valid JSON object
|
||||
- `res.send(body)` - Sends the HTTP response. `body` can be a `string`, an `object` or a `Buffer`
|
||||
- `res.redirect([status,] path)` - Redirects to a specified path or URL. `status` must be a valid [HTTP status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). If not specified, `status` defaults to "307" "Temporary redirect".
|
||||
|
||||
To view an example using types, check out the [TypeScript documentation](/docs/basic-features/typescript.md#api-routes).
|
||||
|
||||
@@ -68,7 +68,6 @@ Let's transform the profile example to use [server-side rendering](/docs/basic-f
|
||||
// pages/profile.js
|
||||
|
||||
import withSession from '../lib/session'
|
||||
import useUser from '../lib/useUser'
|
||||
import Layout from '../components/Layout'
|
||||
|
||||
export const getServerSideProps = withSession(async function ({ req, res }) {
|
||||
@@ -114,16 +113,16 @@ Now that we've discussed authentication patterns, let's look at specific provide
|
||||
<summary><b>Examples</b></summary>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-iron-session">with-iron-session</a></li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-next-auth">with-next-auth</a></li>
|
||||
<li><a href="https://github.com/nextauthjs/next-auth-example">next-auth-example</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
If you have an existing database with user data, you'll likely want to utilize an open-source solution that's provider agnostic.
|
||||
|
||||
- If you need email/password log-in, use [`next-iron-session`](https://github.com/vercel/next.js/tree/canary/examples/with-iron-session).
|
||||
- If you need to persist session data on the server, use [`next-auth`](https://github.com/vercel/next.js/tree/canary/examples/with-next-auth).
|
||||
- If you need to support social login (Google, Facebook, etc.), use [`next-auth`](https://github.com/vercel/next.js/tree/canary/examples/with-next-auth).
|
||||
- If you want to use [JWTs](https://jwt.io/), use [`next-auth`](https://github.com/vercel/next.js/tree/canary/examples/with-next-auth).
|
||||
- If you need to persist session data on the server, use [`next-auth`](https://github.com/nextauthjs/next-auth-example).
|
||||
- If you need to support social login (Google, Facebook, etc.), use [`next-auth`](https://github.com/nextauthjs/next-auth-example).
|
||||
- If you want to use [JWTs](https://jwt.io/), use [`next-auth`](https://github.com/nextauthjs/next-auth-example).
|
||||
|
||||
Both of these libraries support either authentication pattern. If you're interested in [Passport](http://www.passportjs.org/), we also have examples for it using secure and encrypted cookies:
|
||||
|
||||
@@ -189,6 +188,18 @@ You can either use [FirebaseUI](https://github.com/firebase/firebaseui-web-react
|
||||
|
||||
[Userbase](https://userbase.com/) supports the static generation pattern for authentication. It's open source and allows for a high level of security with end-to-end encryption. You can learn more about it in their [official site](https://userbase.com/).
|
||||
|
||||
### SuperTokens
|
||||
|
||||
<details open>
|
||||
<summary><b>Examples</b></summary>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-supertokens">with-supertokens</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
[SuperTokens](https://supertokens.io) is a highly customizable, open-source solution split into modules (so you only use what you need).
|
||||
SuperTokens currently supports credentials login, email verification, password reset flows, and third-party logins.
|
||||
|
||||
## Related
|
||||
|
||||
For more information on what to do next, we recommend the following sections:
|
||||
|
||||
@@ -87,7 +87,7 @@ The `context` parameter is an object containing the following keys:
|
||||
}
|
||||
|
||||
return {
|
||||
props: {}, // will be passed to the page component as props
|
||||
props: { data }, // will be passed to the page component as props
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -111,7 +111,7 @@ The `context` parameter is an object containing the following keys:
|
||||
}
|
||||
|
||||
return {
|
||||
props: {}, // will be passed to the page component as props
|
||||
props: { data }, // will be passed to the page component as props
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -156,7 +156,7 @@ export async function getStaticProps() {
|
||||
const res = await fetch('https://.../posts')
|
||||
const posts = await res.json()
|
||||
|
||||
// By returning { props: posts }, the Blog component
|
||||
// By returning { props: { posts } }, the Blog component
|
||||
// will receive `posts` as a prop at build time
|
||||
return {
|
||||
props: {
|
||||
@@ -325,7 +325,7 @@ export async function getStaticProps() {
|
||||
content: fileContents,
|
||||
}
|
||||
})
|
||||
// By returning { props: posts }, the Blog component
|
||||
// By returning { props: { posts } }, the Blog component
|
||||
// will receive `posts` as a prop at build time
|
||||
return {
|
||||
props: {
|
||||
|
||||
@@ -135,3 +135,15 @@ This one is useful when running tests with tools like `jest` or `cypress` where
|
||||
There is a small difference between `test` environment, and both `development` and `production` that you need to bear in mind: `.env.local` won't be loaded, as you expect tests to produce the same results for everyone. This way every test execution will use same env defaults across different executions by ignoring your `.env.local` (which is intended to override the default set).
|
||||
|
||||
> **Note**: similar to Default Environment Variables, `.env.test` file should be included in your repository, but `.env.test.local` shouldn't, as `.env*.local` are intended to be ignored through `.gitignore`.
|
||||
|
||||
While running unit tests you can make sure to load your environment variables the same way Next.js does by leveraging the `loadEnvConfig` function from the `@next/env` package.
|
||||
|
||||
```js
|
||||
// The below can be used in a Jest global setup file or similar for your testing set-up
|
||||
import { loadEnvConfig } from '@next/env'
|
||||
|
||||
export default async () => {
|
||||
const projectDir = process.cwd()
|
||||
loadEnvConfig(projectDir)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -160,7 +160,9 @@ export async function getStaticPaths() {
|
||||
const posts = await res.json()
|
||||
|
||||
// Get the paths we want to pre-render based on posts
|
||||
const paths = posts.map((post) => `/posts/${post.id}`)
|
||||
const paths = posts.map((post) => ({
|
||||
params: { id: post.id },
|
||||
}))
|
||||
|
||||
// We'll pre-render only these paths at build time.
|
||||
// { fallback: false } means other routes should 404.
|
||||
|
||||
@@ -6,7 +6,7 @@ description: Deploy your Next.js app to production with Vercel and other hosting
|
||||
|
||||
## Vercel (Recommended)
|
||||
|
||||
The easiest way to deploy Next.js to production is to use the **[Vercel platform](https://vercel.com)** from the creators of Next.js. [Vercel](https://vercel.com) is an all-in-one platform with Global CDN supporting static & Jamstack deployment and Serverless Functions.
|
||||
The easiest way to deploy Next.js to production is to use the **[Vercel platform](https://vercel.com)** from the creators of Next.js. [Vercel](https://vercel.com) is a cloud platform for static sites, hybrid apps, and Serverless Functions.
|
||||
|
||||
### Getting started
|
||||
|
||||
@@ -78,6 +78,59 @@ Make sure your `package.json` has the `"build"` and `"start"` scripts:
|
||||
|
||||
`next build` builds the production application in the `.next` folder. After building, `next start` starts a Node.js server that supports [hybrid pages](/docs/basic-features/pages.md), serving both statically generated and server-side rendered pages.
|
||||
|
||||
### Docker Image
|
||||
|
||||
Next.js can be deployed to any hosting provider that supports [Docker](https://www.docker.com/) containers. You can use this approach when deploying to container orchestrators such as [Kubernetes](https://kubernetes.io/) or [HashiCorp Nomad](https://www.nomadproject.io/), or when running inside a single node in any cloud provider.
|
||||
|
||||
Here is a multi-stage `Dockerfile` using `node:alpine` that you can use:
|
||||
|
||||
```Dockerfile
|
||||
# Install dependencies only when needed
|
||||
FROM node:alpine AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
RUN yarn build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM node:alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
# You only need to copy next.config.js if you are NOT using the default configuration
|
||||
# COPY --from=builder /app/next.config.js ./
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/.next ./.next
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
|
||||
RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
RUN chown -R nextjs:nodejs /app/.next
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry.
|
||||
# RUN npx next telemetry disable
|
||||
|
||||
CMD ["node_modules/.bin/next", "start"]
|
||||
```
|
||||
|
||||
Make sure to place this Dockerfile in the root folder of your project.
|
||||
|
||||
You can build your container with `docker build . -t my-next-js-app` and run it with `docker run -p 3000:3000 my-next-js-app`.
|
||||
|
||||
### Static HTML Export
|
||||
|
||||
If you’d like to do a static HTML export of your Next.js app, follow the directions on [our documentation](/docs/advanced-features/static-html-export.md).
|
||||
|
||||
@@ -74,6 +74,8 @@ In the example above we have multiple links, each one maps a path (`href`) to a
|
||||
- `/about` → `pages/about.js`
|
||||
- `/blog/hello-world` → `pages/blog/[slug].js`
|
||||
|
||||
Any `<Link />` in the viewport (initially or through scroll) will be prefetched by default (including the corresponding data) for pages using [Static Generation](/docs/basic-features/data-fetching.md#getstaticprops-static-generation). The corresponding data for [server-rendered](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) routes is _not_ prefetched.
|
||||
|
||||
### Linking to dynamic paths
|
||||
|
||||
You can also use interpolation to create the path, which comes in handy for [dynamic route segments](#dynamic-route-segments). For example, to show a list of posts which have been passed to the component as a prop:
|
||||
|
||||
@@ -11,8 +11,9 @@ This is because Next.js optimizes images on-demand, as users request them (not a
|
||||
#### Possible Ways to Fix It
|
||||
|
||||
- Use `next start` to run a server, which includes the Image Optimization API.
|
||||
- Use Vercel to deploy, which supports [Image Optimization](https://vercel.com/docs/next.js/image-optimization).
|
||||
- Use any provider which supports Image Optimization (like [Vercel](https://vercel.com/docs/next.js/image-optimization)).
|
||||
- Configure a third-party [loader](https://nextjs.org/docs/basic-features/image-optimization#loader) in `next.config.js`.
|
||||
- Use the [`loader`](https://nextjs.org/docs/api-reference/next/image#loader) prop for `next/image`.
|
||||
|
||||
### Useful Links
|
||||
|
||||
|
||||
@@ -111,3 +111,15 @@ Using Heroku's [custom cache](https://devcenter.heroku.com/articles/nodejs-suppo
|
||||
```javascript
|
||||
"cacheDirectories": [".next/cache"]
|
||||
```
|
||||
|
||||
#### Azure Pipelines
|
||||
|
||||
Using Azure Pipelines' [Cache task](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/cache), add the following task to your pipeline yaml file somewhere prior to the task that executes `next build`:
|
||||
|
||||
```yaml
|
||||
- task: Cache@2
|
||||
displayName: 'Cache .next/cache'
|
||||
inputs:
|
||||
key: next | $(Agent.OS) | yarn.lock
|
||||
path: '$(System.DefaultWorkingDirectory)/.next/cache'
|
||||
```
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
In this simple example, we integrate Apollo seamlessly with [Next.js data fetching methods](https://nextjs.org/docs/basic-features/data-fetching) to fetch queries in the server and hydrate them in the browser.
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/api-routes-apollo-server-and-client&project-name=api-routes-apollo-server-and-client&repository-name=api-routes-apollo-server-and-client)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/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:
|
||||
|
||||
4
nextjs/examples/cms-prepr/.env.local.example
Normal file
@@ -0,0 +1,4 @@
|
||||
PREPRIO_API=https://graphql.prepr.io/graphql
|
||||
PREPRIO_PRODUCTION_TOKEN=
|
||||
PREPRIO_PREVIEW_TOKEN=
|
||||
PREPRIO_PREVIEW_KEY=
|
||||
129
nextjs/examples/cms-prepr/README.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# A statically generated blog example using Next.js and Prepr
|
||||
|
||||
This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Prepr](https://prepr.io/) as the data source.
|
||||
|
||||
## Demo
|
||||
|
||||
- **Live**: [https://next-blog-prepr.now.sh/](https://next-blog-prepr.now.sh/)
|
||||
- **Preview Mode**: [https://next-blog-prepr.now.sh/api/preview...](https://next-blog-prepr.now.sh/api/preview?secret=237864ihasdhj283768&slug=discover-enjoy-amsterdam)
|
||||
|
||||
### [https://next-blog-prepr.now.sh/](https://next-blog-prepr.now.sh/)
|
||||
|
||||
### Related examples
|
||||
|
||||
- [WordPress](/examples/cms-wordpress)
|
||||
- [DatoCMS](/examples/cms-datocms)
|
||||
- [Sanity](/examples/cms-sanity)
|
||||
- [TakeShape](/examples/cms-takeshape)
|
||||
- [Prismic](/examples/cms-prismic)
|
||||
- [Contentful](/examples/cms-contentful)
|
||||
- [Strapi](/examples/cms-strapi)
|
||||
- [Agility CMS](/examples/cms-agilitycms)
|
||||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [Kontent](/examples/cms-kontent)
|
||||
- [Ghost](/examples/cms-ghost)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## Getting Started
|
||||
|
||||
Once you have access to [the environment variables you'll need](#step-3-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-prepr&project-name=cms-prepr&repository-name=cms-prepr&env=PREPRIO_API,PREPRIO_PRODUCTION_TOKEN,PREPRIO_PREVIEW_TOKEN,PREPRIO_PREVIEW_KEY&envDescription=Required%20to%20connect%20the%20app%20with%20Prepr&envLink=https://vercel.link/cms-prepr-env)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/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 cms-prepr cms-prepr-app
|
||||
# or
|
||||
yarn create next-app --example cms-prepr cms-prepr-app
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Step 1. Create an account and a environment in Prepr
|
||||
|
||||
First, [create an account in Prepr](https://prepr.io).
|
||||
|
||||
### Step 2. Create Author model
|
||||
|
||||
From your Prepr dashboard, click **Settings** -> **Models**
|
||||
|
||||
Click on the arrow next to **Add model** and select **Import**.
|
||||
|
||||
Import the [`models/author.json`](models/author.json) file.
|
||||
|
||||
After that
|
||||
|
||||
Import the [`models/post.json`](models/post.json) file.
|
||||
|
||||
Click on the Author field and select `Author` at the option `Publication model` and click **Save**.
|
||||
|
||||
### Step 3. Set up environment variables
|
||||
|
||||
Copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
|
||||
|
||||
```bash
|
||||
cp .env.local.example .env.local
|
||||
```
|
||||
|
||||
Inside your environment, navigate to **Settings > Development > Access Tokens**.
|
||||
|
||||
Click **Add access token**, enter the name `Next.js Preview` and add the scope `graphql_preview` and click **Save**.
|
||||
|
||||
Copy the generated access token and set the variable `PREPRIO_PREVIEW_TOKEN` in `.env.local`.
|
||||
|
||||
Go back to the Access token overview and click **Add access token**.
|
||||
|
||||
Enter the name `Next.js Production` and add the scope `graphql_published` and click **Save**.
|
||||
|
||||
Copy the generated access token and set the variable `PREPRIO_PRODUCTION_TOKEN` in `.env.local`.
|
||||
|
||||
The `PREPRIO_PREVIEW_KEY` can be any random string (but avoid spaces), like a UUID`, this is used
|
||||
for [Preview Mode](https://nextjs.org/docs/advanced-features/preview-mode).
|
||||
|
||||
### Step 4. Run Next.js in development mode
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
# or
|
||||
|
||||
yarn install
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions).
|
||||
|
||||
### Step 5. Try preview mode
|
||||
|
||||
In Prepr, go to one of the posts in your environment and:
|
||||
|
||||
- **Update the title**. For example, you can add `[REVIEW]` in front of the title.
|
||||
- After you edit the publication save the post with a review state.
|
||||
|
||||
To view the preview, transform the url to the following format: `http://localhost:3000/api/preview?secret=<YOUR_SECRET_TOKEN>&slug=<SLUG_TO_PREVIEW>` where `<YOUR_SECRET_TOKEN>` is
|
||||
the same secret you defined in the `.env.local` file and `<SLUG_TO_PREVIEW>` is the slug of one of the posts you want to preview.
|
||||
|
||||
You should now be able to see post that are in Review and Done state. To exit the preview mode, you can click on _"Click here to exit preview mode"_ at the top.
|
||||
|
||||
### Step 6. Deploy on Vercel
|
||||
|
||||
You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||
|
||||
#### Deploy Your Local Project
|
||||
|
||||
To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example).
|
||||
|
||||
**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
|
||||
|
||||
#### Deploy from Our Template
|
||||
|
||||
Alternatively, you can deploy using our template by clicking on the Deploy button below.
|
||||
|
||||
[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-prepr&project-name=cms-prepr&repository-name=cms-prepr&env=PREPRIO_API,PREPRIO_PRODUCTION_TOKEN,PREPRIO_PREVIEW_TOKEN,PREPRIO_PREVIEW_KEY&envDescription=Required%20to%20connect%20the%20app%20with%20Prepr&envLink=https://vercel.link/cms-prepr-env)
|
||||
42
nextjs/examples/cms-prepr/components/alert.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import Container from './container'
|
||||
import cn from 'classnames'
|
||||
import { EXAMPLE_PATH } from '../lib/constants'
|
||||
|
||||
export default function Alert({ preview }) {
|
||||
return (
|
||||
<div
|
||||
className={cn('border-b', {
|
||||
'bg-accent-7 border-accent-7 text-white': preview,
|
||||
'bg-accent-1 border-accent-2': !preview,
|
||||
})}
|
||||
>
|
||||
<Container>
|
||||
<div className="py-2 text-center text-sm">
|
||||
{preview ? (
|
||||
<>
|
||||
This page is a preview.{' '}
|
||||
<a
|
||||
href="/api/exit-preview"
|
||||
className="underline hover:text-cyan duration-200 transition-colors"
|
||||
>
|
||||
Click here
|
||||
</a>{' '}
|
||||
to exit preview mode.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
The source code for this blog is{' '}
|
||||
<a
|
||||
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
|
||||
className="underline hover:text-success duration-200 transition-colors"
|
||||
>
|
||||
available on GitHub
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
17
nextjs/examples/cms-prepr/components/avatar.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Avatar({ name, picture }) {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div className="w-12 h-12 relative mr-4">
|
||||
<Image
|
||||
src={picture}
|
||||
layout="fill"
|
||||
className="rounded-full"
|
||||
alt={name}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-xl font-bold">{name}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
3
nextjs/examples/cms-prepr/components/container.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Container({ children }) {
|
||||
return <div className="container mx-auto px-5">{children}</div>
|
||||
}
|
||||
29
nextjs/examples/cms-prepr/components/cover-image.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import cn from 'classnames'
|
||||
|
||||
export default function CoverImage({ title, url, slug }) {
|
||||
const image = (
|
||||
<Image
|
||||
width={2000}
|
||||
height={1000}
|
||||
alt={`Cover Image for ${title}`}
|
||||
className={cn('shadow-small', {
|
||||
'hover:shadow-medium transition-shadow duration-200': slug,
|
||||
})}
|
||||
src={url}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="sm:mx-0">
|
||||
{slug ? (
|
||||
<Link as={`/posts/${slug}`} href="/posts/[slug]">
|
||||
<a aria-label={title}>{image}</a>
|
||||
</Link>
|
||||
) : (
|
||||
image
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
6
nextjs/examples/cms-prepr/components/date.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { parseISO, format } from 'date-fns'
|
||||
|
||||
export default function Date({ dateString }) {
|
||||
const date = parseISO(dateString)
|
||||
return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
|
||||
}
|
||||
30
nextjs/examples/cms-prepr/components/footer.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import Container from './container'
|
||||
import { EXAMPLE_PATH } from '../lib/constants'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-accent-1 border-t border-accent-2">
|
||||
<Container>
|
||||
<div className="py-28 flex flex-col lg:flex-row items-center">
|
||||
<h3 className="text-4xl lg:text-5xl font-bold tracking-tighter leading-tight text-center lg:text-left mb-10 lg:mb-0 lg:pr-4 lg:w-1/2">
|
||||
Statically Generated with Next.js.
|
||||
</h3>
|
||||
<div className="flex flex-col lg:flex-row justify-center items-center lg:pl-4 lg:w-1/2">
|
||||
<a
|
||||
href="https://nextjs.org/docs/basic-features/pages"
|
||||
className="mx-3 bg-black hover:bg-white hover:text-black border border-black text-white font-bold py-3 px-12 lg:px-8 duration-200 transition-colors mb-6 lg:mb-0"
|
||||
>
|
||||
Read Documentation
|
||||
</a>
|
||||
<a
|
||||
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
|
||||
className="mx-3 font-bold hover:underline"
|
||||
>
|
||||
View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
12
nextjs/examples/cms-prepr/components/header.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<h2 className="text-2xl md:text-4xl font-bold tracking-tight md:tracking-tighter leading-tight mb-20 mt-8">
|
||||
<Link href="/">
|
||||
<a className="hover:underline">Blog</a>
|
||||
</Link>
|
||||
.
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
40
nextjs/examples/cms-prepr/components/hero-post.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import Avatar from '../components/avatar'
|
||||
import Date from '../components/date'
|
||||
import CoverImage from '../components/cover-image'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function HeroPost({
|
||||
title,
|
||||
coverImage,
|
||||
date,
|
||||
excerpt,
|
||||
author,
|
||||
slug,
|
||||
}) {
|
||||
return (
|
||||
<section>
|
||||
<div className="mb-8 md:mb-16">
|
||||
<CoverImage slug={slug} title={title} url={coverImage} />
|
||||
</div>
|
||||
<div className="mb-20 md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 md:mb-28">
|
||||
<div>
|
||||
<h3 className="mb-4 text-4xl leading-tight lg:text-6xl">
|
||||
<Link as={`/posts/${slug}`} href="/posts/[slug]">
|
||||
<a className="hover:underline">{title}</a>
|
||||
</Link>
|
||||
</h3>
|
||||
<div className="mb-4 text-lg md:mb-0">
|
||||
<Date dateString={date} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-4 text-lg leading-relaxed">{excerpt}</p>
|
||||
<Avatar
|
||||
name={author.name}
|
||||
picture={author.cover[0].cdn_files[0].url}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
28
nextjs/examples/cms-prepr/components/intro.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { CMS_NAME, CMS_URL } from '../lib/constants'
|
||||
|
||||
export default function Intro() {
|
||||
return (
|
||||
<section className="flex-col md:flex-row flex items-center md:justify-between mt-16 mb-16 md:mb-12">
|
||||
<h1 className="text-6xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8">
|
||||
Blog.
|
||||
</h1>
|
||||
<h4 className="text-center md:text-left text-lg mt-5 md:pl-8">
|
||||
A statically generated blog example using{' '}
|
||||
<a
|
||||
href="https://nextjs.org/"
|
||||
className="underline hover:text-success duration-200 transition-colors"
|
||||
>
|
||||
Next.js
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a
|
||||
href={CMS_URL}
|
||||
className="underline hover:text-success duration-200 transition-colors"
|
||||
>
|
||||
{CMS_NAME}
|
||||
</a>
|
||||
.
|
||||
</h4>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
16
nextjs/examples/cms-prepr/components/layout.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import Alert from '../components/alert'
|
||||
import Footer from '../components/footer'
|
||||
import Meta from '../components/meta'
|
||||
|
||||
export default function Layout({ preview, children }) {
|
||||
return (
|
||||
<>
|
||||
<Meta />
|
||||
<div className="min-h-screen">
|
||||
<Alert preview={preview} />
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
42
nextjs/examples/cms-prepr/components/meta.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import Head from 'next/head'
|
||||
import { CMS_NAME, HOME_OG_IMAGE_URL } from '../lib/constants'
|
||||
|
||||
export default function Meta() {
|
||||
return (
|
||||
<Head>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/favicon/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="/favicon/safari-pinned-tab.svg"
|
||||
color="#000000"
|
||||
/>
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="#000000" />
|
||||
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#000" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
|
||||
<meta
|
||||
name="description"
|
||||
content={`A statically generated blog example using Next.js and ${CMS_NAME}.`}
|
||||
/>
|
||||
<meta property="og:image" content={HOME_OG_IMAGE_URL} />
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
24
nextjs/examples/cms-prepr/components/more-stories.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import PostPreview from '../components/post-preview'
|
||||
|
||||
export default function MoreStories({ posts }) {
|
||||
return (
|
||||
<section>
|
||||
<h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight">
|
||||
More Stories
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-x-16 lg:gap-x-32 gap-y-20 md:gap-y-32 mb-32">
|
||||
{posts.map((post) => (
|
||||
<PostPreview
|
||||
key={post._slug}
|
||||
title={post.title}
|
||||
coverImage={post.cover[0].cdn_files[0].url}
|
||||
date={post.date}
|
||||
author={post.author[0]}
|
||||
slug={post._slug}
|
||||
excerpt={post.summary}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
10
nextjs/examples/cms-prepr/components/post-body.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import postStyles from './post-styles.module.css'
|
||||
|
||||
export default function PostBody({ content }) {
|
||||
return (
|
||||
<div
|
||||
className={`max-w-2xl mx-auto post ${postStyles.post}`}
|
||||
dangerouslySetInnerHTML={{ __html: content }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
29
nextjs/examples/cms-prepr/components/post-header.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import Avatar from '../components/avatar'
|
||||
import Date from '../components/date'
|
||||
import CoverImage from '../components/cover-image'
|
||||
import PostTitle from '../components/post-title'
|
||||
|
||||
export default function PostHeader({ title, coverImage, date, author }) {
|
||||
return (
|
||||
<>
|
||||
<PostTitle>{title}</PostTitle>
|
||||
<div className="hidden md:block md:mb-12">
|
||||
<Avatar name={author.name} picture={author.cover[0].cdn_files[0].url} />
|
||||
</div>
|
||||
<div className="mb-8 -mx-5 md:mb-16 sm:mx-0">
|
||||
<CoverImage title={title} url={coverImage} />
|
||||
</div>
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="block mb-6 md:hidden">
|
||||
<Avatar
|
||||
name={author.name}
|
||||
picture={author.cover[0].cdn_files[0].url}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-6 text-lg">
|
||||
<Date dateString={date} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
31
nextjs/examples/cms-prepr/components/post-preview.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import Avatar from '../components/avatar'
|
||||
import Date from '../components/date'
|
||||
import CoverImage from './cover-image'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function PostPreview({
|
||||
title,
|
||||
coverImage,
|
||||
date,
|
||||
excerpt,
|
||||
author,
|
||||
slug,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-5">
|
||||
<CoverImage slug={slug} title={title} url={coverImage} />
|
||||
</div>
|
||||
<h3 className="mb-3 text-3xl leading-snug">
|
||||
<Link as={`/posts/${slug}`} href="/posts/[slug]">
|
||||
<a className="hover:underline">{title}</a>
|
||||
</Link>
|
||||
</h3>
|
||||
<div className="mb-4 text-lg">
|
||||
<Date dateString={date} />
|
||||
</div>
|
||||
<p className="mb-4 text-lg leading-relaxed">{excerpt}</p>
|
||||
<Avatar name={author.name} picture={author.cover[0].cdn_files[0].url} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
22
nextjs/examples/cms-prepr/components/post-styles.module.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.post {
|
||||
@apply text-lg leading-relaxed;
|
||||
}
|
||||
|
||||
.post p,
|
||||
.post ul,
|
||||
.post ol,
|
||||
.post blockquote {
|
||||
@apply my-6;
|
||||
}
|
||||
|
||||
.post h1 {
|
||||
@apply mt-12 mb-4 text-4xl leading-snug;
|
||||
}
|
||||
|
||||
.post h2 {
|
||||
@apply mt-12 mb-4 text-3xl leading-snug;
|
||||
}
|
||||
|
||||
.post h3 {
|
||||
@apply mt-8 mb-4 text-2xl leading-snug;
|
||||
}
|
||||
7
nextjs/examples/cms-prepr/components/post-title.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function PostTitle({ children }) {
|
||||
return (
|
||||
<h1 className="text-6xl md:text-7xl lg:text-8xl font-bold tracking-tighter leading-tight md:leading-none mb-12 text-center md:text-left">
|
||||
{children}
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function SectionSeparator() {
|
||||
return <hr className="border-accent-2 mt-28 mb-24" />
|
||||
}
|
||||
5
nextjs/examples/cms-prepr/jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
4
nextjs/examples/cms-prepr/lib/constants.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export const EXAMPLE_PATH = 'cms-prepr'
|
||||
export const CMS_NAME = 'Prepr'
|
||||
export const CMS_URL = 'https://prepr.io/'
|
||||
export const HOME_OG_IMAGE_URL = ''
|
||||
156
nextjs/examples/cms-prepr/lib/preprio.js
Normal file
@@ -0,0 +1,156 @@
|
||||
import { createPreprClient } from '@preprio/nodejs-sdk'
|
||||
|
||||
const prepr = createPreprClient({
|
||||
token: process.env.PREPRIO_PRODUCTION_TOKEN,
|
||||
timeout: 4000,
|
||||
baseUrl: process.env.PREPRIO_API,
|
||||
})
|
||||
|
||||
export { prepr }
|
||||
|
||||
export async function getAllPostsForHome(preview) {
|
||||
// Query publications
|
||||
const data =
|
||||
(await prepr
|
||||
.graphqlQuery(
|
||||
`
|
||||
query {
|
||||
Posts {
|
||||
items {
|
||||
_id,
|
||||
_slug,
|
||||
date: _publish_on
|
||||
title,
|
||||
summary,
|
||||
author {
|
||||
name
|
||||
cover {
|
||||
cdn_files {
|
||||
url(width: 100, height:100)
|
||||
}
|
||||
}
|
||||
}
|
||||
cover {
|
||||
cdn_files {
|
||||
url(width:2000, height:1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
)
|
||||
.token(
|
||||
preview
|
||||
? process.env.PREPRIO_PREVIEW_TOKEN
|
||||
: process.env.PREPRIO_PRODUCTION_TOKEN
|
||||
)
|
||||
.fetch()) || []
|
||||
|
||||
return data.data.Posts.items
|
||||
}
|
||||
|
||||
export async function getAllPostsWithSlug() {
|
||||
// Query publications
|
||||
const data =
|
||||
(await prepr
|
||||
.graphqlQuery(
|
||||
`
|
||||
query {
|
||||
Posts {
|
||||
items {
|
||||
slug : _slug,
|
||||
}
|
||||
}
|
||||
}`
|
||||
)
|
||||
.fetch()) || []
|
||||
|
||||
return data.data.Posts.items
|
||||
}
|
||||
|
||||
export async function getPostAndMorePosts(slug, preview) {
|
||||
// Query publications
|
||||
const data =
|
||||
(await prepr
|
||||
.graphqlQuery(
|
||||
`
|
||||
query slugPost($slug: String!) {
|
||||
Post ( slug : $slug) {
|
||||
_id,
|
||||
_slug,
|
||||
date: _publish_on
|
||||
title,
|
||||
summary,
|
||||
content,
|
||||
author {
|
||||
name
|
||||
cover {
|
||||
cdn_files {
|
||||
url(width: 100, height:100)
|
||||
}
|
||||
}
|
||||
}
|
||||
cover {
|
||||
cdn_files {
|
||||
url(width:2000, height:1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
morePosts : Posts(where : { _slug_nany : [$slug] }) {
|
||||
items {
|
||||
_id,
|
||||
_slug,
|
||||
date: _publish_on
|
||||
title,
|
||||
summary,
|
||||
author {
|
||||
name
|
||||
cover {
|
||||
cdn_files {
|
||||
url(width: 100, height:100)
|
||||
}
|
||||
}
|
||||
}
|
||||
cover {
|
||||
cdn_files {
|
||||
url(width:2000, height:1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
)
|
||||
.graphqlVariables({
|
||||
slug: slug,
|
||||
})
|
||||
.token(
|
||||
preview
|
||||
? process.env.PREPRIO_PREVIEW_TOKEN
|
||||
: process.env.PREPRIO_PRODUCTION_TOKEN
|
||||
)
|
||||
.fetch()) || []
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function getPreviewPostBySlug(slug) {
|
||||
// Query publications
|
||||
const data =
|
||||
(await prepr
|
||||
.graphqlQuery(
|
||||
`
|
||||
query preview($slug: String!) {
|
||||
Post ( slug : $slug) {
|
||||
_id,
|
||||
slug : _slug
|
||||
}
|
||||
}`
|
||||
)
|
||||
.token(process.env.PREPRIO_PREVIEW_TOKEN)
|
||||
.graphqlVariables({
|
||||
slug: slug,
|
||||
})
|
||||
.fetch()) || []
|
||||
|
||||
return data.data.Post
|
||||
}
|
||||
67
nextjs/examples/cms-prepr/models/author.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"id": "f7153965-7bc9-4b9b-b964-49129b34c5e8",
|
||||
"created_on": "2021-03-04T12:03:46+00:00",
|
||||
"changed_on": "2021-03-04T12:03:46+00:00",
|
||||
"body": "Author",
|
||||
"description": null,
|
||||
"label": "PublicationModel",
|
||||
"status": "published",
|
||||
"stories": false,
|
||||
"timelines": false,
|
||||
"allow_stories": false,
|
||||
"channels": false,
|
||||
"allow_channels": false,
|
||||
"container_required": false,
|
||||
"channels_required": false,
|
||||
"seo_score": false,
|
||||
"ab_testing": false,
|
||||
"versioning": false,
|
||||
"slug": null,
|
||||
"for": null,
|
||||
"fields": [
|
||||
{
|
||||
"type": "Text",
|
||||
"required": false,
|
||||
"preview": true,
|
||||
"body": "Name",
|
||||
"view": "single_line",
|
||||
"localized": true,
|
||||
"id": "fd7f062f-84e4-4362-9e15-61c1a26a4078",
|
||||
"api_id": "name",
|
||||
"created_on": "2021-03-04T12:03:55+00:00",
|
||||
"changed_on": "2021-03-04T12:03:55+00:00",
|
||||
"label": "PublicationModelField",
|
||||
"title": false,
|
||||
"seo_title": false,
|
||||
"seo_description": false,
|
||||
"description": null,
|
||||
"disable_editing": false,
|
||||
"appearance": "single_line",
|
||||
"accept_restrictions": null,
|
||||
"no_html": false
|
||||
},
|
||||
{
|
||||
"accept": ["photo"],
|
||||
"max": "1",
|
||||
"preview": true,
|
||||
"type": "Asset",
|
||||
"min": "1",
|
||||
"required": true,
|
||||
"body": "Cover Image",
|
||||
"localized": true,
|
||||
"id": "fd81a8e2-2f59-44b7-b3b2-873a27958db1",
|
||||
"api_id": "cover",
|
||||
"created_on": "2021-03-04T12:04:13+00:00",
|
||||
"changed_on": "2021-03-04T12:04:13+00:00",
|
||||
"label": "PublicationModelField",
|
||||
"title": false,
|
||||
"seo_title": false,
|
||||
"seo_description": false,
|
||||
"description": null,
|
||||
"disable_editing": false,
|
||||
"appearance": null,
|
||||
"accept_restrictions": null,
|
||||
"presets_change_required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
158
nextjs/examples/cms-prepr/models/post.json
Normal file
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"id": "4da8ca3d-86c2-40ba-84c0-5ea949293b9b",
|
||||
"created_on": "2021-03-04T12:02:44+00:00",
|
||||
"changed_on": "2021-03-04T12:10:37+00:00",
|
||||
"body": "Post",
|
||||
"description": null,
|
||||
"label": "PublicationModel",
|
||||
"status": "published",
|
||||
"stories": false,
|
||||
"timelines": false,
|
||||
"allow_stories": false,
|
||||
"channels": false,
|
||||
"allow_channels": false,
|
||||
"container_required": false,
|
||||
"channels_required": false,
|
||||
"seo_score": false,
|
||||
"ab_testing": false,
|
||||
"versioning": false,
|
||||
"slug": null,
|
||||
"for": null,
|
||||
"fields": [
|
||||
{
|
||||
"type": "Text",
|
||||
"required": false,
|
||||
"preview": true,
|
||||
"body": "Title",
|
||||
"view": "single_line",
|
||||
"localized": true,
|
||||
"id": "7aa96451-d3b5-41e9-b012-77f66dd2213b",
|
||||
"api_id": "title",
|
||||
"created_on": "2021-03-04T12:02:49+00:00",
|
||||
"changed_on": "2021-03-04T12:02:49+00:00",
|
||||
"label": "PublicationModelField",
|
||||
"title": false,
|
||||
"seo_title": false,
|
||||
"seo_description": false,
|
||||
"description": null,
|
||||
"disable_editing": false,
|
||||
"appearance": "single_line",
|
||||
"accept_restrictions": null,
|
||||
"no_html": false
|
||||
},
|
||||
{
|
||||
"type": "Text",
|
||||
"required": false,
|
||||
"preview": true,
|
||||
"body": "Summary",
|
||||
"view": "textarea",
|
||||
"localized": true,
|
||||
"id": "ea6a35ca-5a39-4786-9cf7-15e917a0dcf4",
|
||||
"api_id": "summary",
|
||||
"created_on": "2021-03-04T12:02:59+00:00",
|
||||
"changed_on": "2021-03-04T12:02:59+00:00",
|
||||
"label": "PublicationModelField",
|
||||
"title": false,
|
||||
"seo_title": false,
|
||||
"seo_description": false,
|
||||
"description": null,
|
||||
"disable_editing": false,
|
||||
"appearance": "textarea",
|
||||
"accept_restrictions": null,
|
||||
"no_html": false
|
||||
},
|
||||
{
|
||||
"accept": [
|
||||
"bold",
|
||||
"italic",
|
||||
"underline",
|
||||
"unordered-list",
|
||||
"ordered-list",
|
||||
"link",
|
||||
"table"
|
||||
],
|
||||
"preview": true,
|
||||
"view": "html_editor",
|
||||
"type": "Text",
|
||||
"required": false,
|
||||
"body": "Content",
|
||||
"localized": true,
|
||||
"id": "cd31e930-a08c-4740-903e-5c41fcd1c555",
|
||||
"api_id": "content",
|
||||
"created_on": "2021-03-04T12:04:36+00:00",
|
||||
"changed_on": "2021-03-04T12:04:36+00:00",
|
||||
"label": "PublicationModelField",
|
||||
"title": false,
|
||||
"seo_title": false,
|
||||
"seo_description": false,
|
||||
"description": null,
|
||||
"disable_editing": false,
|
||||
"appearance": "html_editor",
|
||||
"accept_restrictions": null,
|
||||
"no_html": false
|
||||
},
|
||||
{
|
||||
"accept": ["photo"],
|
||||
"max": "1",
|
||||
"preview": true,
|
||||
"type": "Asset",
|
||||
"min": "1",
|
||||
"required": true,
|
||||
"body": "Cover Image",
|
||||
"localized": true,
|
||||
"id": "cfca83f3-5a73-4567-bde1-4adeb57915d0",
|
||||
"api_id": "cover",
|
||||
"created_on": "2021-03-04T12:03:17+00:00",
|
||||
"changed_on": "2021-03-04T12:03:17+00:00",
|
||||
"label": "PublicationModelField",
|
||||
"title": false,
|
||||
"seo_title": false,
|
||||
"seo_description": false,
|
||||
"description": null,
|
||||
"disable_editing": false,
|
||||
"appearance": null,
|
||||
"accept_restrictions": null,
|
||||
"presets_change_required": false
|
||||
},
|
||||
{
|
||||
"type": "Publication",
|
||||
"min": "1",
|
||||
"required": true,
|
||||
"max": "1",
|
||||
"preview": false,
|
||||
"body": "Author",
|
||||
"localized": true,
|
||||
"id": "8308380f-af0a-40e7-ae30-33b2f7ce93d9",
|
||||
"api_id": "author",
|
||||
"created_on": "2021-03-04T12:04:51+00:00",
|
||||
"changed_on": "2021-03-04T12:04:51+00:00",
|
||||
"label": "PublicationModelField",
|
||||
"title": false,
|
||||
"seo_title": false,
|
||||
"seo_description": false,
|
||||
"description": null,
|
||||
"disable_editing": false,
|
||||
"appearance": null,
|
||||
"accept_restrictions": null
|
||||
},
|
||||
{
|
||||
"type": "Slug",
|
||||
"description": "The slug is generated automatically. You can change the slug, as long as it is unique.",
|
||||
"body": "Slug",
|
||||
"id": "7214e365-1b4c-4de3-8412-e311250fb4da",
|
||||
"api_id": null,
|
||||
"created_on": "2021-03-04T12:03:32+00:00",
|
||||
"changed_on": "2021-03-04T12:03:32+00:00",
|
||||
"label": "PublicationModelField",
|
||||
"title": false,
|
||||
"seo_title": false,
|
||||
"seo_description": false,
|
||||
"required": false,
|
||||
"localized": false,
|
||||
"preview": false,
|
||||
"disable_editing": false,
|
||||
"appearance": null,
|
||||
"accept_restrictions": null
|
||||
}
|
||||
]
|
||||
}
|
||||
5
nextjs/examples/cms-prepr/next.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
images: {
|
||||
domains: ['b-cdn.net'],
|
||||
},
|
||||
}
|
||||
25
nextjs/examples/cms-prepr/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "cms-prepr",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@preprio/nodejs-sdk": "^1.1.0",
|
||||
"autoprefixer": "10.1.0",
|
||||
"classnames": "2.2.6",
|
||||
"date-fns": "2.10.0",
|
||||
"next": "latest",
|
||||
"postcss": "8.2.2",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"tailwindcss": "2.0.2"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
7
nextjs/examples/cms-prepr/pages/_app.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import '../styles/index.css'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
|
||||
export default MyApp
|
||||
15
nextjs/examples/cms-prepr/pages/_document.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
8
nextjs/examples/cms-prepr/pages/api/exit-preview.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default async function handler(_, res) {
|
||||
// Exit the current user from "Preview Mode". This function accepts no args.
|
||||
res.clearPreviewData()
|
||||
|
||||
// Redirect the user back to the index page.
|
||||
res.writeHead(307, { Location: '/' })
|
||||
res.end()
|
||||
}
|
||||
25
nextjs/examples/cms-prepr/pages/api/preview.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { getPreviewPostBySlug } from '../../lib/preprio'
|
||||
|
||||
export default async function handler(req, res) {
|
||||
// Check the secret and next parameters
|
||||
// This secret should only be known to this API route and the CMS
|
||||
if (req.query.secret !== process.env.PREPRIO_PREVIEW_KEY || !req.query.slug) {
|
||||
return res.status(401).json({ message: 'Invalid token' })
|
||||
}
|
||||
|
||||
// Fetch the headless CMS to check if the provided `slug` exists
|
||||
const post = await getPreviewPostBySlug(req.query.slug)
|
||||
|
||||
// If the slug doesn't exist prevent preview mode from being enabled
|
||||
if (!post) {
|
||||
return res.status(401).json({ message: 'Invalid slug' })
|
||||
}
|
||||
|
||||
// Enable Preview Mode by setting the cookies
|
||||
res.setPreviewData({})
|
||||
|
||||
// Redirect to the path from the fetched post
|
||||
// We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
|
||||
res.writeHead(307, { Location: `/posts/${post.slug}` })
|
||||
res.end()
|
||||
}
|
||||
44
nextjs/examples/cms-prepr/pages/index.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import Container from '../components/container'
|
||||
import MoreStories from '../components/more-stories'
|
||||
import HeroPost from '../components/hero-post'
|
||||
import Intro from '../components/intro'
|
||||
import Layout from '../components/layout'
|
||||
import { getAllPostsForHome } from '../lib/preprio'
|
||||
import Head from 'next/head'
|
||||
import { CMS_NAME } from '../lib/constants'
|
||||
|
||||
export default function Index({ posts, preview }) {
|
||||
const heroPost = posts[0]
|
||||
const morePosts = posts.slice(1)
|
||||
return (
|
||||
<>
|
||||
<Layout preview={preview}>
|
||||
<Head>
|
||||
<title>Next.js Blog Example with {CMS_NAME}</title>
|
||||
</Head>
|
||||
<Container>
|
||||
<Intro />
|
||||
{heroPost && (
|
||||
<HeroPost
|
||||
title={heroPost.title}
|
||||
coverImage={heroPost.cover[0].cdn_files[0].url}
|
||||
date={heroPost.date}
|
||||
author={heroPost.author[0]}
|
||||
slug={heroPost._slug}
|
||||
excerpt={heroPost.summary}
|
||||
/>
|
||||
)}
|
||||
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
|
||||
</Container>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps({ preview = false }) {
|
||||
const posts = (await getAllPostsForHome(preview)) || []
|
||||
|
||||
return {
|
||||
props: { posts, preview },
|
||||
}
|
||||
}
|
||||
75
nextjs/examples/cms-prepr/pages/posts/[slug].js
Normal file
@@ -0,0 +1,75 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import ErrorPage from 'next/error'
|
||||
import Container from 'components/container'
|
||||
import PostBody from 'components/post-body'
|
||||
import MoreStories from 'components/more-stories'
|
||||
import Header from 'components/header'
|
||||
import PostHeader from 'components/post-header'
|
||||
import SectionSeparator from 'components/section-separator'
|
||||
import Layout from 'components/layout'
|
||||
import { getAllPostsWithSlug, getPostAndMorePosts } from 'lib/preprio'
|
||||
import PostTitle from 'components/post-title'
|
||||
import Head from 'next/head'
|
||||
import { CMS_NAME } from 'lib/constants'
|
||||
|
||||
export default function Post({ post, morePosts, preview }) {
|
||||
const router = useRouter()
|
||||
|
||||
if (!router.isFallback && !post?._slug) {
|
||||
return <ErrorPage statusCode={404} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout preview={preview}>
|
||||
<Container>
|
||||
<Header />
|
||||
{router.isFallback ? (
|
||||
<PostTitle>Loading…</PostTitle>
|
||||
) : (
|
||||
<>
|
||||
<article>
|
||||
<Head>
|
||||
<title>
|
||||
{post.title} | Next.js Blog Example with {CMS_NAME}
|
||||
</title>
|
||||
{/* <meta property="og:image" content={post.ogImage.url} /> */}
|
||||
</Head>
|
||||
<PostHeader
|
||||
title={post.title}
|
||||
coverImage={post.cover[0].cdn_files[0].url}
|
||||
date={post.date}
|
||||
author={post.author[0]}
|
||||
/>
|
||||
<PostBody content={post.content} />
|
||||
</article>
|
||||
<SectionSeparator />
|
||||
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params, preview = false }) {
|
||||
const data = await getPostAndMorePosts(params.slug, preview)
|
||||
|
||||
return {
|
||||
props: {
|
||||
preview,
|
||||
post: data.Post,
|
||||
morePosts: data.morePosts.items || [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getAllPostsWithSlug()
|
||||
|
||||
return {
|
||||
paths: posts.map(({ slug }) => ({
|
||||
params: { slug },
|
||||
})),
|
||||
fallback: true,
|
||||
}
|
||||
}
|
||||
18
nextjs/examples/cms-prepr/postcss.config.js
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'tailwindcss',
|
||||
'postcss-flexbugs-fixes',
|
||||
[
|
||||
'postcss-preset-env',
|
||||
{
|
||||
autoprefixer: {
|
||||
flexbox: 'no-2009',
|
||||
},
|
||||
stage: 3,
|
||||
features: {
|
||||
'custom-properties': false,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 14 KiB |
BIN
nextjs/examples/cms-prepr/public/favicon/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/favicons/mstile-150x150.png"/>
|
||||
<TileColor>#000000</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
nextjs/examples/cms-prepr/public/favicon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 595 B |
BIN
nextjs/examples/cms-prepr/public/favicon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 880 B |
BIN
nextjs/examples/cms-prepr/public/favicon/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
nextjs/examples/cms-prepr/public/favicon/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M4785 10234 c-22 -2 -92 -9 -155 -14 -1453 -131 -2814 -915 -3676
|
||||
-2120 -480 -670 -787 -1430 -903 -2235 -41 -281 -46 -364 -46 -745 0 -381 5
|
||||
-464 46 -745 278 -1921 1645 -3535 3499 -4133 332 -107 682 -180 1080 -224
|
||||
155 -17 825 -17 980 0 687 76 1269 246 1843 539 88 45 105 57 93 67 -8 6 -383
|
||||
509 -833 1117 l-818 1105 -1025 1517 c-564 834 -1028 1516 -1032 1516 -4 1 -8
|
||||
-673 -10 -1496 -3 -1441 -4 -1499 -22 -1533 -26 -49 -46 -69 -88 -91 -32 -16
|
||||
-60 -19 -211 -19 l-173 0 -46 29 c-30 19 -52 44 -67 73 l-21 45 2 2005 3 2006
|
||||
31 39 c16 21 50 48 74 61 41 20 57 22 230 22 204 0 238 -8 291 -66 15 -16 570
|
||||
-852 1234 -1859 664 -1007 1572 -2382 2018 -3057 l810 -1227 41 27 c363 236
|
||||
747 572 1051 922 647 743 1064 1649 1204 2615 41 281 46 364 46 745 0 381 -5
|
||||
464 -46 745 -278 1921 -1645 3535 -3499 4133 -327 106 -675 179 -1065 223 -96
|
||||
10 -757 21 -840 13z m2094 -3094 c48 -24 87 -70 101 -118 8 -26 10 -582 8
|
||||
-1835 l-3 -1798 -317 486 -318 486 0 1307 c0 845 4 1320 10 1343 16 56 51 100
|
||||
99 126 41 21 56 23 213 23 148 0 174 -2 207 -20z"/>
|
||||
<path d="M7843 789 c-35 -22 -46 -37 -15 -20 22 13 58 40 52 41 -3 0 -20 -10
|
||||
-37 -21z"/>
|
||||
<path d="M7774 744 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7724 714 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7674 684 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7598 644 c-38 -20 -36 -28 2 -9 17 9 30 18 30 20 0 7 -1 6 -32 -11z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
19
nextjs/examples/cms-prepr/public/favicon/site.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Next.js",
|
||||
"short_name": "Next.js",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#000000",
|
||||
"display": "standalone"
|
||||
}
|
||||
5
nextjs/examples/cms-prepr/styles/index.css
Normal file
@@ -0,0 +1,5 @@
|
||||
/* purgecss start ignore */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
/* purgecss end ignore */
|
||||
@tailwind utilities;
|
||||
33
nextjs/examples/cms-prepr/tailwind.config.js
Normal file
@@ -0,0 +1,33 @@
|
||||
module.exports = {
|
||||
purge: ['./components/**/*.js', './pages/**/*.js'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'accent-1': '#FAFAFA',
|
||||
'accent-2': '#EAEAEA',
|
||||
'accent-7': '#333',
|
||||
success: '#0070f3',
|
||||
cyan: '#79FFE1',
|
||||
},
|
||||
spacing: {
|
||||
28: '7rem',
|
||||
},
|
||||
letterSpacing: {
|
||||
tighter: '-.04em',
|
||||
},
|
||||
lineHeight: {
|
||||
tight: 1.2,
|
||||
},
|
||||
fontSize: {
|
||||
'5xl': '2.5rem',
|
||||
'6xl': '2.75rem',
|
||||
'7xl': '4.5rem',
|
||||
'8xl': '6.25rem',
|
||||
},
|
||||
boxShadow: {
|
||||
small: '0 5px 10px rgba(0, 0, 0, 0.12)',
|
||||
medium: '0 8px 30px rgba(0, 0, 0, 0.12)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -43,7 +43,7 @@ yarn create next-app --example cms-strapi cms-strapi-app
|
||||
|
||||
### Step 1. Set up Strapi locally
|
||||
|
||||
[Follow the instructions on this page](https://strapi.io/documentation/v3.x/installation/cli.html) to create a Strapi project locally.
|
||||
[Follow the instructions on this page](https://strapi.io/documentation/developer-docs/latest/getting-started/quick-start.html#_1-install-strapi-and-create-a-new-project) to create a Strapi project locally.
|
||||
|
||||
```bash
|
||||
npx create-strapi-app my-project --quickstart
|
||||
|
||||
@@ -9,7 +9,7 @@ exports['default'] = {
|
||||
secure: false,
|
||||
// Passed to https.createServer if secure=true. Should contain SSL certificates
|
||||
serverOptions: {},
|
||||
// Should we redirect all traffic to the first host in this array if hte request header doesn't match?
|
||||
// Should we redirect all traffic to the first host in this array if the request header doesn't match?
|
||||
// i.e.: [ 'https://www.site.com' ]
|
||||
allowedRequestHosts: process.env.ALLOWED_HOSTS
|
||||
? process.env.ALLOWED_HOSTS.split(',')
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "latest",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1"
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
27
nextjs/examples/styled-jsx-with-csp/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Styled-JSX with Content Security Policy
|
||||
|
||||
This example showcases how you can use `nonce` for `style-src` directive in `Content Security Policy` with `styled-jsx`.
|
||||
|
||||
Checkout the [demo](https://styled-jsx-with-csp.vercel.app/) and notice the following,
|
||||
|
||||
- `style-src` directive in `Content-Security-Policy` response header.
|
||||
- `meta` tag to pass on the `nonce` to styled-jsx for client-side rendering.
|
||||
- `style` tags with `nonce` attributes.
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/styled-jsx-with-csp&project-name=styled-jsx-with-csp&repository-name=styled-jsx-with-csp)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/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 styled-jsx-with-csp styled-jsx-with-csp-app
|
||||
# or
|
||||
yarn create next-app --example styled-jsx-with-csp styled-jsx-with-csp-app
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||
16
nextjs/examples/styled-jsx-with-csp/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "styled-jsx-with-csp",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"nanoid": "3.1.21",
|
||||
"next": "10.0.8",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1"
|
||||
}
|
||||
}
|
||||
6
nextjs/examples/styled-jsx-with-csp/pages/_app.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
const CustomApp = ({ Component, pageProps }) => <Component {...pageProps} />
|
||||
|
||||
// Disable static optimization to always server render, making nonce unique on every request
|
||||
CustomApp.getInitialProps = () => ({})
|
||||
|
||||
export default CustomApp
|
||||
49
nextjs/examples/styled-jsx-with-csp/pages/_document.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
import flush from 'styled-jsx/server'
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
class CustomDocument extends Document {
|
||||
static async getInitialProps(ctx) {
|
||||
const nonce = nanoid()
|
||||
|
||||
// https://github.com/vercel/next.js/blob/canary/packages/next/pages/_document.tsx#L89
|
||||
const { html, head } = await ctx.renderPage()
|
||||
|
||||
// Adds `nonce` to style tags on Server Side Rendering
|
||||
const styles = [...flush({ nonce })]
|
||||
|
||||
let contentSecurityPolicy = ''
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
contentSecurityPolicy = `default-src 'self'; style-src 'nonce-${nonce}';`
|
||||
} else {
|
||||
// react-refresh needs 'unsafe-eval'
|
||||
// Next.js needs 'unsafe-inline' during development https://github.com/vercel/next.js/blob/canary/packages/next/client/dev/fouc.js
|
||||
// Specifying 'nonce' makes a modern browsers ignore 'unsafe-inline'
|
||||
contentSecurityPolicy = `default-src 'self'; style-src 'unsafe-inline'; script-src 'self' 'unsafe-eval';`
|
||||
}
|
||||
|
||||
ctx.res.setHeader('Content-Security-Policy', contentSecurityPolicy)
|
||||
|
||||
return { styles, html, head, nonce }
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
{/* Styled-JSX will add this `nonce` to style tags on Client Side Rendering */}
|
||||
{/* https://github.com/vercel/styled-jsx/blob/master/src/lib/stylesheet.js#L31 */}
|
||||
{/* https://github.com/vercel/styled-jsx/blob/master/src/lib/stylesheet.js#L240 */}
|
||||
<meta property="csp-nonce" content={this.props.nonce} />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomDocument
|
||||
40
nextjs/examples/styled-jsx-with-csp/pages/index.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
const ClientSideComponent = () => (
|
||||
<>
|
||||
<style jsx>
|
||||
{`
|
||||
.title {
|
||||
font-size: 24px;
|
||||
color: green;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<p className="title">This is rendered on client-side</p>
|
||||
</>
|
||||
)
|
||||
|
||||
const Home = () => {
|
||||
const [isVisible, setVisibility] = useState(false)
|
||||
|
||||
const toggleVisibility = () => {
|
||||
setVisibility((prevState) => !prevState)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx>
|
||||
{`
|
||||
.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<p className="title">Styled-JSX with Content Security Policy</p>
|
||||
<button onClick={toggleVisibility}>Toggle</button>
|
||||
{isVisible ? <ClientSideComponent /> : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
@@ -7,12 +7,12 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/icons": "^1.0.0",
|
||||
"@chakra-ui/react": "^1.0.0",
|
||||
"@chakra-ui/theme-tools": "1.0.0",
|
||||
"@emotion/react": "11.1.1",
|
||||
"@emotion/styled": "11.0.0",
|
||||
"framer-motion": "^2.9.4",
|
||||
"@chakra-ui/icons": "^1.0.5",
|
||||
"@chakra-ui/react": "^1.3.3",
|
||||
"@chakra-ui/theme-tools": "1.0.4",
|
||||
"@emotion/react": "11.1.5",
|
||||
"@emotion/styled": "11.1.5",
|
||||
"framer-motion": "^3.5.2",
|
||||
"next": "latest",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
|
||||
4
nextjs/examples/with-compiled-css/.babelrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["next/babel"],
|
||||
"plugins": ["@babel/plugin-syntax-jsx"]
|
||||
}
|
||||
34
nextjs/examples/with-compiled-css/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
@@ -1,21 +1,21 @@
|
||||
# @zeit/fetch example
|
||||
# Example app with [Compiled](https://github.com/atlassian-labs/compiled) (CSS-in-JS)
|
||||
|
||||
This example shows how to use [`@zeit/fetch`](https://npmjs.com/package/@zeit/fetch) in a Next.js application.
|
||||
This example features how to use [Compiled](https://github.com/atlassian-labs/compiled) as the build time CSS-in-JS styling solution instead of [styled-jsx](https://github.com/zeit/styled-jsx).
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-zeit-fetch&project-name=with-zeit-fetch&repository-name=with-zeit-fetch)
|
||||
[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-compiled-css&project-name=with-compiled-css&repository-name=with-compiled-css)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/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-zeit-fetch with-zeit-fetch-app
|
||||
npx create-next-app --example with-compiled-css with-compiled-css-app
|
||||
# or
|
||||
yarn create next-app --example with-zeit-fetch with-zeit-fetch-app
|
||||
yarn create next-app --example with-compiled-css with-compiled-css-app
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ClassNames } from '@compiled/react'
|
||||
import { error } from '../style/colors'
|
||||
|
||||
export const BoxStyles = ({ children }) => (
|
||||
<ClassNames>
|
||||
{({ css }) =>
|
||||
children({
|
||||
className: css`
|
||||
display: flex;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px solid ${error};
|
||||
padding: 8px;
|
||||
flex-direction: column;
|
||||
`,
|
||||
})
|
||||
}
|
||||
</ClassNames>
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
import { styled } from '@compiled/react'
|
||||
|
||||
export const Button = styled.button`
|
||||
color: ${(props) => props.color};
|
||||
background-color: transparent;
|
||||
padding: 6px 8px;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
font-family: sans-serif;
|
||||
border: 1px solid ${(props) => props.color};
|
||||
`
|
||||