commit
4337285481
15 changed files with 1651 additions and 1375 deletions
|
|
@ -16,6 +16,7 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
rules: {
|
||||
"tailwindcss/no-custom-classname": "off",
|
||||
"testing-library/prefer-screen-queries": "off",
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
|
|
|
|||
1
.github/workflows/check.yml
vendored
1
.github/workflows/check.yml
vendored
|
|
@ -5,6 +5,7 @@ on:
|
|||
branches:
|
||||
- main
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
|
|
|
|||
4
.github/workflows/nextjs_bundle_analysis.yml
vendored
4
.github/workflows/nextjs_bundle_analysis.yml
vendored
|
|
@ -4,9 +4,11 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
|
|
@ -49,7 +51,7 @@ jobs:
|
|||
# Here's the first place where next-bundle-analysis' own script is used
|
||||
# This step pulls the raw bundle stats for the current bundle
|
||||
- name: Analyze bundle
|
||||
run: npx -p nextjs-bundle-analysis report
|
||||
run: node report-bundle-size.js
|
||||
|
||||
- name: Upload bundle
|
||||
uses: actions/upload-artifact@v3
|
||||
|
|
|
|||
1
.github/workflows/playwright.yml
vendored
1
.github/workflows/playwright.yml
vendored
|
|
@ -4,6 +4,7 @@ on:
|
|||
branches:
|
||||
- main
|
||||
- master
|
||||
- develop
|
||||
pull_request: null
|
||||
workflow_dispatch: null
|
||||
jobs:
|
||||
|
|
|
|||
3
app/api/health/route.ts
Normal file
3
app/api/health/route.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export async function GET() {
|
||||
return Response.json({ status: "ok" })
|
||||
}
|
||||
9
app/layout.tsx
Normal file
9
app/layout.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import "styles/tailwind.css"
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,22 +1,27 @@
|
|||
import { GetServerSidePropsContext } from "next"
|
||||
import Head from "next/head"
|
||||
import { Metadata } from "next"
|
||||
import { Button } from "components/Button/Button"
|
||||
import { LP_GRID_ITEMS } from "../lp-items"
|
||||
import { LP_GRID_ITEMS } from "lp-items"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Next.js Enterprise Boilerplate",
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
},
|
||||
openGraph: {
|
||||
url: "https://next-enterprise.vercel.app/",
|
||||
images: [
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
url: "https://raw.githubusercontent.com/Blazity/next-enterprise/main/project-logo.png",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default function Web() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta property="og:url" content="https://next-enterprise.vercel.app/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://raw.githubusercontent.com/Blazity/next-enterprise/main/project-logo.png"
|
||||
/>
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<title>Next.js Enterprise Boilerplate</title>
|
||||
</Head>
|
||||
<section className="bg-white dark:bg-gray-900">
|
||||
<div className="mx-auto grid max-w-screen-xl px-4 py-8 text-center lg:py-16">
|
||||
<div className="mx-auto place-self-center">
|
||||
|
|
@ -58,18 +63,3 @@ export default function Web() {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, res }: GetServerSidePropsContext) {
|
||||
if (req.headers?.host?.includes("next-enterprise.vercel.app")) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: "https://blazity.com/open-source/nextjs-enterprise-boilerplate",
|
||||
permanent: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {},
|
||||
}
|
||||
}
|
||||
22
middleware.ts
Normal file
22
middleware.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { NextResponse } from "next/server"
|
||||
import type { NextRequest } from "next/server"
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
// TODO: Feel free to remove this block
|
||||
if (request.headers?.get("host")?.includes("next-enterprise.vercel.app")) {
|
||||
return NextResponse.redirect("https://blazity.com/open-source/nextjs-enterprise-boilerplate", { status: 301 })
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - api (API routes)
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
*/
|
||||
"/((?!api|_next/static|_next/image|favicon.ico).*)",
|
||||
],
|
||||
}
|
||||
81
package.json
81
package.json
|
|
@ -23,7 +23,7 @@
|
|||
"coupling-graph": "npx madge --extensions js,jsx,ts,tsx,css,md,mdx ./ --exclude '.next|tailwind.config.js|reset.d.ts|prettier.config.js|postcss.config.js|playwright.config.ts|next.config.js|next-env.d.ts|instrumentation.ts|e2e/|README.md|.storybook/|.eslintrc.js' --image graph.svg"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/bundle-analyzer": "^13.5.5",
|
||||
"@next/bundle-analyzer": "^14.0.3",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
|
|
@ -40,76 +40,79 @@
|
|||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^11.0.0",
|
||||
"@semantic-release/commit-analyzer": "^11.1.0",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/github": "^9.2.1",
|
||||
"@semantic-release/npm": "^11.0.0",
|
||||
"@semantic-release/release-notes-generator": "^12.0.0",
|
||||
"@semantic-release/github": "^9.2.3",
|
||||
"@semantic-release/npm": "^11.0.1",
|
||||
"@semantic-release/release-notes-generator": "^12.1.0",
|
||||
"@t3-oss/env-nextjs": "^0.7.1",
|
||||
"@vercel/otel": "^0.3.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "^13.5.5",
|
||||
"next": "^14.0.3",
|
||||
"next-compose-plugins": "^2.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/plugin-syntax-flow": "^7.22.5",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.23.0",
|
||||
"@babel/plugin-transform-react-jsx": "^7.22.15",
|
||||
"@babel/core": "^7.23.3",
|
||||
"@babel/plugin-syntax-flow": "^7.23.3",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.23.4",
|
||||
"@babel/plugin-transform-react-jsx": "^7.23.4",
|
||||
"@jest/globals": "^29.7.0",
|
||||
"@opentelemetry/api": "1.4.1",
|
||||
"@opentelemetry/resources": "1.12.0",
|
||||
"@opentelemetry/sdk-node": "0.38.0",
|
||||
"@opentelemetry/sdk-trace-node": "1.12.0",
|
||||
"@opentelemetry/semantic-conventions": "1.12.0",
|
||||
"@playwright/test": "^1.39.0",
|
||||
"@storybook/addon-essentials": "^7.4.6",
|
||||
"@storybook/addon-interactions": "^7.4.6",
|
||||
"@storybook/addon-links": "^7.4.6",
|
||||
"@storybook/blocks": "^7.4.6",
|
||||
"@storybook/nextjs": "^7.4.6",
|
||||
"@storybook/react": "^7.4.6",
|
||||
"@storybook/test-runner": "^0.13.0",
|
||||
"@opentelemetry/api": "1.7.0",
|
||||
"@opentelemetry/resources": "1.18.1",
|
||||
"@opentelemetry/sdk-node": "0.45.1",
|
||||
"@opentelemetry/sdk-trace-node": "1.18.1",
|
||||
"@opentelemetry/semantic-conventions": "1.18.1",
|
||||
"@playwright/test": "^1.40.0",
|
||||
"@storybook/addon-essentials": "^7.5.3",
|
||||
"@storybook/addon-interactions": "^7.5.3",
|
||||
"@storybook/addon-links": "^7.5.3",
|
||||
"@storybook/blocks": "^7.5.3",
|
||||
"@storybook/nextjs": "^7.5.3",
|
||||
"@storybook/react": "^7.5.3",
|
||||
"@storybook/test-runner": "^0.15.2",
|
||||
"@storybook/testing-library": "^0.2.2",
|
||||
"@testing-library/jest-dom": "^6.1.4",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/node": "^20.8.6",
|
||||
"@types/react": "^18.2.28",
|
||||
"@types/react-dom": "^18.2.13",
|
||||
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
||||
"@typescript-eslint/parser": "^6.8.0",
|
||||
"@types/jest": "^29.5.10",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/react": "^18.2.38",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"all-contributors-cli": "^6.26.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "8.51.0",
|
||||
"eslint-config-next": "13.5.5",
|
||||
"eslint": "8.54.0",
|
||||
"eslint-config-next": "14.0.3",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-react": "7.33.2",
|
||||
"eslint-plugin-storybook": "^0.6.15",
|
||||
"eslint-plugin-tailwindcss": "^3.13.0",
|
||||
"fetch-mock": "^9.11.0",
|
||||
"gzip-size": "6",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"mkdirp": "^3.0.1",
|
||||
"npm-only-allow": "^1.2.6",
|
||||
"patch-package": "^8.0.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "latest",
|
||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||
"semantic-release": "^22.0.5",
|
||||
"storybook": "^7.4.6",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"prettier": "3.0.3",
|
||||
"prettier-plugin-tailwindcss": "^0.5.7",
|
||||
"semantic-release": "^22.0.8",
|
||||
"storybook": "^7.5.3",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tsc": "^2.0.4",
|
||||
"typescript": "5.1.6",
|
||||
"typescript": "5.3.2",
|
||||
"webpack": "5.89.0"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
import { render } from "@testing-library/react"
|
||||
import { AppProps } from "next/app"
|
||||
import MyApp from "./_app"
|
||||
|
||||
// Mock router object with required properties
|
||||
const router = {
|
||||
basePath: "",
|
||||
pathname: "/",
|
||||
query: {},
|
||||
asPath: "/",
|
||||
}
|
||||
|
||||
describe("MyApp", () => {
|
||||
it("should render a component with page props", () => {
|
||||
const props: AppProps = {
|
||||
Component: ({ title }) => (
|
||||
<>
|
||||
<div>Test Component</div>
|
||||
<div>{title}</div>
|
||||
</>
|
||||
),
|
||||
pageProps: { title: "Test Page" },
|
||||
router: router as never, // Include mocked router object in props
|
||||
}
|
||||
const { getByText } = render(<MyApp {...props} />)
|
||||
expect(getByText("Test Component")).toBeInTheDocument()
|
||||
expect(getByText("Test Page")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import "../styles/tailwind.css"
|
||||
|
||||
import { AppProps } from "next/app"
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
|
||||
export default MyApp
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res.status(200).json({ status: "ok" })
|
||||
}
|
||||
133
report-bundle-size.js
Normal file
133
report-bundle-size.js
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env node
|
||||
/* eslint-disable no-console */
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*/
|
||||
|
||||
// edited to work with the appdir by @raphaelbadia
|
||||
|
||||
const gzSize = require("gzip-size")
|
||||
const mkdirp = require("mkdirp")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
// Pull options from `package.json`
|
||||
const options = getOptions()
|
||||
const BUILD_OUTPUT_DIRECTORY = getBuildOutputDirectory(options)
|
||||
|
||||
// first we check to make sure that the build output directory exists
|
||||
const nextMetaRoot = path.join(process.cwd(), BUILD_OUTPUT_DIRECTORY)
|
||||
try {
|
||||
fs.accessSync(nextMetaRoot, fs.constants.R_OK)
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`No build output found at "${nextMetaRoot}" - you may not have your working directory set correctly, or not have run "next build".`
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// if so, we can import the build manifest
|
||||
const buildMeta = require(path.join(nextMetaRoot, "build-manifest.json"))
|
||||
const appDirMeta = require(path.join(nextMetaRoot, "app-build-manifest.json"))
|
||||
|
||||
// this memory cache ensures we dont read any script file more than once
|
||||
// bundles are often shared between pages
|
||||
const memoryCache = {}
|
||||
|
||||
// since _app is the template that all other pages are rendered into,
|
||||
// every page must load its scripts. we'll measure its size here
|
||||
const globalBundle = buildMeta.pages["/_app"]
|
||||
const globalBundleSizes = getScriptSizes(globalBundle)
|
||||
|
||||
// next, we calculate the size of each page's scripts, after
|
||||
// subtracting out the global scripts
|
||||
const allPageSizes = Object.values(buildMeta.pages).reduce((acc, scriptPaths, i) => {
|
||||
const pagePath = Object.keys(buildMeta.pages)[i]
|
||||
const scriptSizes = getScriptSizes(scriptPaths.filter((scriptPath) => !globalBundle.includes(scriptPath)))
|
||||
|
||||
acc[pagePath] = scriptSizes
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const globalAppDirBundle = buildMeta.rootMainFiles
|
||||
const globalAppDirBundleSizes = getScriptSizes(globalAppDirBundle)
|
||||
|
||||
const allAppDirSizes = Object.values(appDirMeta.pages).reduce((acc, scriptPaths, i) => {
|
||||
const pagePath = Object.keys(appDirMeta.pages)[i]
|
||||
const scriptSizes = getScriptSizes(scriptPaths.filter((scriptPath) => !globalAppDirBundle.includes(scriptPath)))
|
||||
acc[pagePath] = scriptSizes
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
// format and write the output
|
||||
const rawData = JSON.stringify({
|
||||
...allAppDirSizes,
|
||||
__global: globalAppDirBundleSizes,
|
||||
})
|
||||
|
||||
// log ouputs to the gh actions panel
|
||||
console.log(rawData)
|
||||
|
||||
mkdirp.sync(path.join(nextMetaRoot, "analyze/"))
|
||||
fs.writeFileSync(path.join(nextMetaRoot, "analyze/__bundle_analysis.json"), rawData)
|
||||
|
||||
// --------------
|
||||
// Util Functions
|
||||
// --------------
|
||||
|
||||
// given an array of scripts, return the total of their combined file sizes
|
||||
function getScriptSizes(scriptPaths) {
|
||||
const res = scriptPaths.reduce(
|
||||
(acc, scriptPath) => {
|
||||
const [rawSize, gzipSize] = getScriptSize(scriptPath)
|
||||
acc.raw += rawSize
|
||||
acc.gzip += gzipSize
|
||||
|
||||
return acc
|
||||
},
|
||||
{ raw: 0, gzip: 0 }
|
||||
)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// given an individual path to a script, return its file size
|
||||
function getScriptSize(scriptPath) {
|
||||
const encoding = "utf8"
|
||||
const p = path.join(nextMetaRoot, scriptPath)
|
||||
|
||||
let rawSize, gzipSize
|
||||
if (Object.keys(memoryCache).includes(p)) {
|
||||
rawSize = memoryCache[p][0]
|
||||
gzipSize = memoryCache[p][1]
|
||||
} else {
|
||||
const textContent = fs.readFileSync(p, encoding)
|
||||
rawSize = Buffer.byteLength(textContent, encoding)
|
||||
gzipSize = gzSize.sync(textContent)
|
||||
memoryCache[p] = [rawSize, gzipSize]
|
||||
}
|
||||
|
||||
return [rawSize, gzipSize]
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads options from `package.json`
|
||||
*/
|
||||
function getOptions(pathPrefix = process.cwd()) {
|
||||
const pkg = require(path.join(pathPrefix, "package.json"))
|
||||
|
||||
return { ...pkg.nextBundleAnalysis, name: pkg.name }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output build directory, defaults to `.next`
|
||||
*
|
||||
* @param {object} options the options parsed from package.json.nextBundleAnalysis using `getOptions`
|
||||
* @returns {string}
|
||||
*/
|
||||
function getBuildOutputDirectory(options) {
|
||||
return options.buildOutputDirectory || ".next"
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
|
|
@ -16,8 +17,14 @@
|
|||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"types": ["node", "jest", "@testing-library/jest-dom"],
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.mjs", "jest.config.js"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.mjs", "jest.config.js", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue