1
0
mirror of synced 2026-02-03 18:01:02 -05:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Dillon Raphael
106efcb1d4 fix pathds 2022-09-22 21:40:45 -04:00
Dillon Raphael
1b1c7a2c7b downgrade mdx loader 2022-09-22 18:20:32 -04:00
Dillon Raphael
5ebc891534 import prism jsx language 2022-09-22 16:32:20 -04:00
Dillon Raphael
d01e5e8048 pnpmlock 2022-09-22 13:30:41 -04:00
Dillon Raphael
b13a24503f Add back docs mdx files 2022-09-22 13:18:56 -04:00
Dillon Raphael
1fe5d0b4c8 fix lint issue 2022-09-22 13:14:45 -04:00
Dillon Raphael
6d7cfddf51 migrate website over 2022-09-20 19:12:00 -04:00
277 changed files with 32873 additions and 201 deletions

View File

@@ -5,7 +5,8 @@
"workspaces": [
"apps/*",
"packages/*",
"integration-tests/*"
"integration-tests/*",
"website"
],
"scripts": {
"preinstall": "npx only-allow pnpm",

7442
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,3 +2,4 @@ packages:
- "apps/*"
- "packages/*"
- "integration-tests/*"
- "website"

15
website/.alexrc.js Normal file
View File

@@ -0,0 +1,15 @@
// Use a "maybe" level of profanity instead of the default "unlikely".
exports.profanitySureness = 1
exports.allow = [
"simple",
"special",
"invalid",
"he-she",
"her-him",
"herself-himself",
"obvious",
"host",
"dad-mom",
"host-hostess",
]

1
website/.eslintrc.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require("@blitzjs/next/eslint")

56
website/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# dependencies
node_modules
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.pnp.*
.npm
web_modules/
# blitz
/.blitz/
/.next/
*.sqlite
*.sqlite-journal
.now
.blitz**
blitz-log.log
# misc
.DS_Store
# local env files
.env.local
.env.*.local
.envrc
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Testing
.coverage
*.lcov
.nyc_output
lib-cov
# Caches
*.tsbuildinfo
.eslintcache
.node_repl_history
.yarn-integrity
# Serverless directories
.serverless/
# Stores VSCode versions used for testing VSCode extensions
.vscode-test

View File

@@ -0,0 +1,13 @@
const Banner = ({message, hasLightBg, className = ""}) => (
<div
className={`border-b border-opacity-50 border-primary ${
hasLightBg ? "text-black dark:text-dark-mode-text" : "text-white"
} ${className}`}
>
<div className="font-semibold max-w-7xl mx-auto pt-1 pb-2 md:pt-0 md:pb-3 px-3 sm:px-6 lg:px-8 text-sm text-center">
{message}
</div>
</div>
)
export default Banner

View File

@@ -0,0 +1,36 @@
import React from "react"
import {BsArrowRight} from "react-icons/bs"
const ButtonLink = React.forwardRef(({className, children, variant = "solid", ...props}, ref) => {
let classes = "flex items-center justify-center py-2 px-3 lg:px-5 font-secondary font-bold"
if (!className.includes("text-")) {
classes += " text-base"
}
switch (variant) {
case "solid":
classes +=
" bg-white text-off-black hover:bg-gradient-to-r from-blue-gradient-white to-blue-gradient-light-blue"
break
case "solid-dark":
classes +=
" bg-purple-light dark:bg-purple-primary text-white hover:bg-purple-mid dark:hover:bg-purple-dark"
break
case "outline":
classes +=
" border border-white text-white hover:bg-purple-primary dark:hover:bg-purple-off-black"
break
default:
throw new Error("Invalid variant value: " + variant)
}
return (
<a className={`${classes} ${className}`} {...props} ref={ref}>
{children} <BsArrowRight size="1.5rem" className="ml-2" />
</a>
)
})
ButtonLink.displayName = "ButtonLink"
export {ButtonLink}

View File

@@ -0,0 +1,51 @@
export function Token({token, parentTypes, children}) {
return <span className={`token ${token[0]}`}>{children}</span>
}
export function Code({
tokens,
parentTypes = [],
transformTokens = (x) => x,
tokenProps = {},
tokenComponent: TokenComponent = Token,
}) {
const tokensArr = Array.isArray(tokens) ? tokens : [tokens]
return tokensArr.map((token, i) => {
const t = transformTokens(token, tokensArr, i)
if (typeof t === "string") return t
if (t[0] === parentTypes[parentTypes.length - 1]) {
return (
<Code
key={i}
tokens={t[1]}
parentTypes={parentTypes}
tokenComponent={TokenComponent}
tokenProps={tokenProps}
transformTokens={transformTokens}
/>
)
}
return (
<TokenComponent
key={i}
token={t}
tokenIndex={i}
tokens={tokensArr}
parentTypes={parentTypes}
{...tokenProps}
>
<Code
tokens={t[1]}
parentTypes={[...parentTypes, t[0]]}
tokenComponent={TokenComponent}
tokenProps={tokenProps}
transformTokens={transformTokens}
/>
</TokenComponent>
)
})
}

View File

@@ -0,0 +1,99 @@
import {forwardRef, useMemo} from "react"
import {Code} from "@/components/Code"
import styles from "./CodeWindow.module.css"
export function CodeWindow({
children,
lineNumbersBackground = true,
className = "",
tabs,
onTabClick,
}) {
return (
<div
className={`relative overflow-hidden rounded-xl shadow-2xl flex ${styles.root} ${className}`}
>
<div className="absolute inset-0 bg-white dark:bg-purple-off-black" />
<div className="relative flex flex-col w-full">
<div className="flex items-center flex-none px-4 bg-gray-200 h-9 dark:bg-transparent">
<div className="flex space-x-1.5">
<div className="w-3 h-3 bg-red-500 rounded-full" />
<div className="w-3 h-3 rounded-full bg-amber-400" />
<div className="w-3 h-3 bg-green-400 rounded-full" />
</div>
<div className="file-bar flex self-end h-full pt-2 overflow-x-scroll hide-scrollbar">
{tabs.map((tab, i) => (
<button
key={i}
onClick={() => onTabClick(i)}
className={`px-4 ml-3 text-xs text-black dark:text-dark-mode-text rounded-t-lg font-mono ${
tab.selected
? "bg-gray-50 dark:bg-purple-mid"
: "bg-gray-300 dark:bg-purple-extradark"
}`}
>
{tab.title}
</button>
))}
</div>
</div>
<div className="relative flex flex-col flex-auto min-h-0 border-t border-gray-200 dark:border-gray-800">
{lineNumbersBackground && (
<div className="absolute inset-y-0 left-0 hidden md:block" style={{width: 50}} />
)}
{children}
</div>
</div>
</div>
)
}
CodeWindow.Code = forwardRef(({tokens, initialLineNumber = 1, ...props}, ref) => {
const lineNumbers = useMemo(() => {
const t = tokens.flat(Infinity)
let line = initialLineNumber + 1
let str = `${initialLineNumber}\n`
for (let i = 0; i < t.length; i++) {
if (typeof t[i] === "string") {
const newLineChars = t[i].match(/\n/g)
if (newLineChars !== null) {
for (let j = 0; j < newLineChars.length; j++) {
str += `${line++}\n`
}
}
}
}
return str
}, [tokens, initialLineNumber])
return (
<div className="flex flex-auto w-full min-h-0 overflow-auto">
<div ref={ref} className="relative flex-auto w-full">
<pre className="flex min-h-full text-xs">
<div
aria-hidden="true"
className="flex-none hidden py-4 pr-4 font-mono text-right text-black text-opacity-50 select-none dark:text-dark-mode-text md:block"
style={{width: 50}}
>
{lineNumbers}
</div>
<code className="relative flex-auto block px-4 pt-4 pb-4 overflow-auto font-mono text-black dark:text-dark-mode-text text-xs">
<Code tokens={tokens} {...props} />
</code>
</pre>
</div>
</div>
)
})
CodeWindow.Code.displayName = "CodeWindow"
export function getClassNameForToken({types, empty}) {
const typesSize = types.length
if (typesSize === 1 && types[0] === "plain") {
return empty ? "inline-block" : undefined
}
return (types[typesSize - 1] + (empty ? " inline-block" : " token")).trim()
}

View File

@@ -0,0 +1,11 @@
.root {
height: 28rem;
max-height: 60vh;
}
@screen md {
.root {
height: 34rem;
max-height: none;
}
}

View File

@@ -0,0 +1,17 @@
const DarkModeLogo = (props) => {
return (
<svg viewBox="0 0 165 66" {...props}>
<path d="M104.292 56.033C104.292 56.408 104.206 56.6636 104.036 56.8C103.9 56.9363 103.627 57.0045 103.218 57.0045H99.7409C99.4001 57.0045 99.1615 56.9533 99.0251 56.8511C98.8888 56.7147 98.8206 56.4932 98.8206 56.1864L98.9229 19.8324C98.9229 19.3211 99.1444 19.0654 99.5876 19.0654H103.627C103.839 19.0654 104.292 19.0672 104.292 19.0672V19.8324V56.033ZM64.3531 57.0081C64.1145 57.0081 63.927 56.9399 63.7906 56.8035C63.6543 56.6672 63.5861 56.4968 63.5861 56.2922V19.9383C63.5861 19.3588 63.8588 19.069 64.4042 19.069H76.829C81.533 19.069 85.1463 19.9212 87.6687 21.6256C90.1912 23.2958 91.4524 25.7331 91.4524 28.9373C91.4524 30.9484 90.924 32.6528 89.8673 34.0504C88.8106 35.4138 87.1063 36.5217 84.7543 37.3739C84.6179 37.4079 84.5497 37.4932 84.5497 37.6295C84.5497 37.7318 84.6179 37.7999 84.7543 37.834C87.2767 38.5158 89.1686 39.5895 90.4298 41.0553C91.7251 42.521 92.3727 44.4469 92.3727 46.833C92.3727 50.2418 91.0945 52.7983 88.5379 54.5027C85.9814 56.1729 82.2318 57.0081 77.2892 57.0081H64.3531ZM77.5448 35.5843C79.6923 35.5843 81.516 35.1071 83.0158 34.1526C84.5157 33.1982 85.2656 31.6983 85.2656 29.6531C85.2656 27.6079 84.5157 26.0569 83.0158 25.0002C81.5501 23.9435 79.5219 23.4151 76.9313 23.4151H70.5399C70.0286 23.4151 69.7729 23.6367 69.7729 24.0798V34.8684C69.7729 35.3457 69.9604 35.5843 70.3354 35.5843H77.5448ZM77.0335 52.662C82.9647 52.662 85.9303 50.5997 85.9303 46.4751C85.9303 44.3276 85.1633 42.7255 83.6294 41.6688C82.0955 40.6121 80.0673 40.0838 77.5448 40.0838H70.591C70.2843 40.0838 70.0627 40.1349 69.9263 40.2372C69.8241 40.3394 69.7729 40.5099 69.7729 40.7485V51.895C69.7729 52.4063 69.9604 52.662 70.3354 52.662H77.0335ZM142.707 56.8624C142.81 56.9647 142.997 57.0158 143.27 57.0158H163.876C164.387 57.0158 164.643 56.7772 164.643 56.3V53.948V53.3344H163.978H149.866C149.593 53.3344 149.457 53.2492 149.457 53.0788C149.457 52.9765 149.508 52.8572 149.61 52.7208L163.876 33.8536C164.251 33.2741 164.438 32.7628 164.438 32.3197V30.479V29.9144C164.438 29.9144 164.051 29.9165 163.876 29.9165H144.241C143.866 29.9165 143.679 30.121 143.679 30.5301V32.831C143.679 33.1037 143.713 33.2911 143.781 33.3934C143.883 33.4957 144.071 33.5468 144.344 33.5468H157.075C157.382 33.5468 157.535 33.632 157.535 33.8025L157.382 34.1092L143.219 52.9765C142.946 53.3515 142.759 53.6412 142.656 53.8457C142.588 54.0502 142.554 54.3059 142.554 54.6127V56.3C142.554 56.5727 142.605 56.7602 142.707 56.8624ZM116.929 19.0676H111.51V27.7684C114.503 27.7684 116.929 25.3419 116.929 22.3486V19.0676ZM116.926 56.0308C116.926 56.4058 116.841 56.6614 116.67 56.7978C116.534 56.9341 116.278 57.0023 115.903 57.0023H112.427C112.086 57.0023 111.847 56.9512 111.711 56.8489C111.574 56.7126 111.506 56.491 111.506 56.1842V30.6699C111.506 30.3972 111.557 30.2098 111.66 30.1075C111.762 29.9712 111.949 29.903 112.222 29.903H117.028L116.926 56.0308ZM132.183 34.3137C132.183 33.9728 132.336 33.8024 132.643 33.8024H138.779C139.256 33.8024 139.495 33.5979 139.495 33.1888V30.4789V29.9165H138.881H132.745C132.439 29.9165 132.285 29.7631 132.285 29.4563V21.531V20.713L131.621 20.7129H128.093C127.752 20.7129 127.547 20.9515 127.479 21.4288L126.865 29.4563C126.865 29.7631 126.729 29.9165 126.456 29.9165H122.366C121.957 29.9165 121.752 30.1039 121.752 30.4789V33.1888C121.752 33.5979 121.974 33.8024 122.417 33.8024H126.252C126.593 33.8024 126.763 34.0069 126.763 34.416V50.6244C126.763 52.806 127.309 54.4252 128.399 55.4819C129.49 56.5045 131.16 57.0158 133.41 57.0158C135.796 57.0158 137.535 56.9306 138.625 56.7601C139.137 56.6579 139.392 56.3681 139.392 55.8909V53.6923V53.0787H138.779H135.507C134.348 53.0787 133.495 52.806 132.95 52.2606C132.439 51.7152 132.183 50.7267 132.183 49.295V34.3137Z" />
<path
d="M0.241243 33.2639H10.9742C15.0585 33.2639 18.9054 35.1835 21.3612 38.4471L31.9483 52.5165C32.1484 52.7824 32.1786 53.1393 32.026 53.435L25.9232 65.2592C25.6304 65.8265 24.8455 65.8932 24.4612 65.3835L0.241243 33.2639Z"
fill="#6700EB"
/>
<path
d="M42.4727 33.2822H31.7398C27.6555 33.2822 23.8086 31.3626 21.3528 28.0991L10.7656 14.0297C10.5656 13.7638 10.5354 13.4068 10.688 13.1111L16.7908 1.28696C17.0836 0.719654 17.8684 0.652924 18.2528 1.16266L42.4727 33.2822Z"
fill="#6700EB"
/>
</svg>
)
}
export default DarkModeLogo

View File

@@ -0,0 +1,36 @@
import {useTheme} from "next-themes"
import {useEffect, useState} from "react"
import {BiToggleLeft, BiToggleRight} from "react-icons/bi"
const DarkModeToggle = ({className}) => {
const [mounted, setMounted] = useState(false)
const {theme, setTheme} = useTheme()
// When mounted on client, now we can show the UI
useEffect(() => setMounted(true), [])
if (!mounted) return null
const switchTheme = () => {
setTheme(theme === "dark" ? "light" : "dark")
}
return (
<button
onClick={switchTheme}
className={`pr-2 rounded focus:outline-none focus:ring-inset focus:ring-white ${className}`}
>
{theme === "dark" ? (
<BiToggleRight size="2rem" className="inline" />
) : (
<BiToggleLeft size="2rem" className="inline" />
)}
<span className="mx-1">
{theme === "dark" ? "Dark" : "Light"}
<span className="lg:hidden"> Mode</span>
</span>
</button>
)
}
export {DarkModeToggle}

View File

@@ -0,0 +1,203 @@
import Link from "next/link"
import {useRouter} from "next/router"
import {useEffect, useState} from "react"
import {AiOutlineClose, AiOutlineMenu} from "react-icons/ai"
import {FaDiscord, FaGithub, FaTwitter} from "react-icons/fa"
import {FaHeart} from "react-icons/fa"
import {FiArrowUpRight} from "react-icons/fi"
import {HiExternalLink} from "react-icons/hi"
import Banner from "@/components/Banner"
import ColoredLogo from "@/components/ColoredLogo"
import {DarkModeToggle} from "@/components/DarkModeToggle"
import Logo from "@/components/Logo"
import {NavLink} from "@/components/NavLink"
import {Search} from "@/components/Search"
import {useIsDesktop} from "@/hooks/useIsDesktop"
const SocialIcons = ({className, variant}) => {
const outerClasses = variant === "bright" ? "bg-purple-light dark:bg-white" : "bg-white "
const innerClasses =
variant === "bright"
? "text-white dark:text-purple-mid"
: "text-purple-primary dark:text-purple-dark"
return (
<div className={`flex items-center space-x-3 ${className}`}>
<a
href="https://github.com/blitz-js/blitz"
target="_blank"
rel="noopener noreferrer"
className={"rounded-full w-7 h-7 flex items-center justify-center " + outerClasses}
>
<FaGithub className={" " + innerClasses} size="1rem" />
</a>
<a
href="https://twitter.com/blitz_js"
target="_blank"
rel="noopener noreferrer"
className={"rounded-full w-7 h-7 flex items-center justify-center " + outerClasses}
>
<FaTwitter className={" " + innerClasses} size="1rem" />
</a>
<a
href="https://discord.blitzjs.com"
target="_blank"
rel="noopener noreferrer"
className={"rounded-full w-7 h-7 flex items-center justify-center " + outerClasses}
>
<FaDiscord className={" " + innerClasses} size="1rem" />
</a>
</div>
)
}
const bannerMsg = (
<div>
🚀
<a
href="https://flightcontrol.dev?ref=blitzjs"
rel="noreferrer"
target="_blank"
className="underline"
>
Announcing Flightcontrol
</a>{" "}
- Easily Deploy Blitz.js and Next.js to AWS 🚀
</div>
)
const Header = ({
className = "",
hasLightBg,
useColoredLogo,
stickyBgClass,
hasFade,
onNavToggle,
}) => {
const router = useRouter()
const isDesktop = useIsDesktop()
let [isOpen, setIsOpen] = useState(false)
useEffect(() => {
if (!isOpen) return
function handleRouteChange() {
setIsOpen(false)
}
router.events.on("routeChangeComplete", handleRouteChange)
return () => {
router.events.off("routeChangeComplete", handleRouteChange)
}
}, [isOpen, router.events])
const onToggle = () => {
const newValue = !isOpen
setIsOpen(newValue)
onNavToggle(newValue)
}
const menuLinks = [
{
name: "Documentation",
href: isDesktop ? "/docs/get-started" : "/docs",
},
{
name: "Showcase",
href: "/showcase",
},
{name: "Releases", href: "https://github.com/blitz-js/blitz/releases"},
{name: "Swag", href: "https://store.blitzjs.com"},
{name: "Deploy with Flightcontrol", href: "https://flightcontrol.dev?ref=blitzjs"},
]
return (
<>
{bannerMsg && <Banner message={bannerMsg} hasLightBg={hasLightBg} className="pt-3" />}
<nav className={`${stickyBgClass ? "sticky top-0 z-50" : ""}`}>
<div className={`flex items-center justify-between lg:mt-4 ${className} ${stickyBgClass}`}>
<div className="pr-8 xl:pr-12 lg:-mt-3">
<Link href="/">
<a className="w-10 overflow-hidden md:w-auto">
<span className="sr-only">Blitz home page</span>
{useColoredLogo && (
<ColoredLogo className="w-auto h-12 py-2 fill-current inline dark:hidden" />
)}
<Logo
className={`w-auto h-12 py-2 fill-current ${
useColoredLogo ? "hidden dark:inline" : ""
}`}
/>
</a>
</Link>
</div>
<div className="flex-1 hidden space-x-4 xl:space-x-6 text-base lg:flex">
{menuLinks.map((link) => {
const external = link.href.startsWith("http")
const props = external ? {target: "_blank", rel: "noopener noreferrer"} : {}
return (
<NavLink href={link.href} key={link.href + link.name} {...props}>
{link.name}
{external && (
<FiArrowUpRight size="0.65rem" className="opacity-40 absolute top-2 right-0" />
)}
</NavLink>
)
})}
</div>
<div className="flex lg:text-base xl:space-x-4">
<Search className="self-center" />
<button
onClick={onToggle}
className="p-2 transition-opacity rounded-md lg:hidden focus:ring-2 focus:outline-none focus:ring-inset focus:ring-white"
>
{isOpen ? <AiOutlineClose size="1.375rem" /> : <AiOutlineMenu size="1.375rem" />}
</button>
<DarkModeToggle className="hidden text-base lg:my-2 lg:block" />
<SocialIcons
className="hidden lg:flex"
variant={useColoredLogo ? "bright" : "normal"}
/>
</div>
</div>
{isOpen && (
<div
className={`h-screen pt-4 text-2xl lg:hidden dark:bg-purple-deep space-y-1 ${className} ${
useColoredLogo ? "bg-white" : ""
}`}
>
{menuLinks.map((link) => {
const external = link.href.startsWith("http")
const props = external ? {target: "_blank", rel: "noopener noreferrer"} : {}
return (
<NavLink href={link.href} key={link.href + link.name} {...props}>
{link.name}
{external && (
<HiExternalLink size="1rem" className="opacity-70 absolute top-3 right-0" />
)}
</NavLink>
)
})}
<NavLink
target="_blank"
rel="noopener noreferrer"
href="https://github.com/sponsors/blitz-js"
>
<FaHeart size="1rem" className="inline mr-1 mb-1 align-text-center" /> Donate/Sponsor
<HiExternalLink size="1rem" className="opacity-70 absolute top-3 right-0" />
</NavLink>
<div className="py-2">
<div className="border-t border-black dark:border-off-white border-opacity-50"></div>
</div>
<div className="space-y-3">
<DarkModeToggle className="text-lg -ml-3" />
<SocialIcons variant={useColoredLogo ? "bright" : "normal"} />
</div>
</div>
)}
{hasFade && (
<div className="absolute bg-gradient-to-b from-white dark:from-purple-deep h-12 lg:block pointer-events-none w-full z-10"></div>
)}
</nav>
</>
)
}
export {Header}

View File

@@ -0,0 +1,71 @@
import clsx from "clsx"
import {useContext, useEffect, useRef} from "react"
import {useTop} from "@/hooks/useTop"
import {ContentsContext} from "../layouts/ContentsLayout"
export function Heading({
level,
id,
children,
number,
badge,
className = "",
hidden = false,
toc = false,
style = {},
...props
}) {
let Component = `h${level}`
const {registerHeading, unregisterHeading} = useContext(ContentsContext)
let ref = useRef()
let top = useTop(ref)
useEffect(() => {
if (toc && typeof top !== "undefined") {
registerHeading(id, top)
}
return () => {
unregisterHeading(id)
}
}, [toc, top, id, registerHeading, unregisterHeading])
return (
<Component
className={clsx("group flex whitespace-pre-wrap", className)}
id={id}
// ref={ref}
style={{...(hidden ? {marginBottom: 0} : {}), ...style}}
{...props}
>
{!hidden && (
// eslint-disable-next-line
<a
href={`#${id}`}
className="absolute after:hash opacity-0 group-hover:opacity-100"
style={{
marginLeft: "-1em",
paddingRight: "0.5em",
boxShadow: "none",
color: "#6700EB",
textDecoration: "none",
}}
aria-label="Anchor"
/>
)}
{number && (
<span className="bg-cyan-100 w-8 h-8 inline-flex items-center justify-center rounded-full text-cyan-700 text-xl mr-3 flex-none">
{number}
</span>
)}
<span className={hidden ? "sr-only" : undefined}>{children}</span>
{badge && (
<span className="ml-3 inline-flex items-center px-3 py-1 rounded-full text-sm font-medium leading-4 bg-green-150 text-green-900">
{badge}
</span>
)}
</Component>
)
}

View File

@@ -0,0 +1,11 @@
const Logo = (props) => {
return (
<svg viewBox="0 0 165 66" {...props}>
<path d="M104.292 56.033C104.292 56.408 104.206 56.6636 104.036 56.8C103.9 56.9363 103.627 57.0045 103.218 57.0045H99.7409C99.4001 57.0045 99.1615 56.9533 99.0251 56.8511C98.8888 56.7147 98.8206 56.4932 98.8206 56.1864L98.9229 19.8324C98.9229 19.3211 99.1444 19.0654 99.5876 19.0654H103.627C103.839 19.0654 104.292 19.0672 104.292 19.0672V19.8324V56.033ZM64.3531 57.0081C64.1145 57.0081 63.927 56.9399 63.7906 56.8035C63.6543 56.6672 63.5861 56.4968 63.5861 56.2922V19.9383C63.5861 19.3588 63.8588 19.069 64.4042 19.069H76.829C81.533 19.069 85.1463 19.9212 87.6687 21.6256C90.1912 23.2958 91.4524 25.7331 91.4524 28.9373C91.4524 30.9484 90.924 32.6528 89.8673 34.0504C88.8106 35.4138 87.1063 36.5217 84.7543 37.3739C84.6179 37.4079 84.5497 37.4932 84.5497 37.6295C84.5497 37.7318 84.6179 37.7999 84.7543 37.834C87.2767 38.5158 89.1686 39.5895 90.4298 41.0553C91.7251 42.521 92.3727 44.4469 92.3727 46.833C92.3727 50.2418 91.0945 52.7983 88.5379 54.5027C85.9814 56.1729 82.2318 57.0081 77.2892 57.0081H64.3531ZM77.5448 35.5843C79.6923 35.5843 81.516 35.1071 83.0158 34.1526C84.5157 33.1982 85.2656 31.6983 85.2656 29.6531C85.2656 27.6079 84.5157 26.0569 83.0158 25.0002C81.5501 23.9435 79.5219 23.4151 76.9313 23.4151H70.5399C70.0286 23.4151 69.7729 23.6367 69.7729 24.0798V34.8684C69.7729 35.3457 69.9604 35.5843 70.3354 35.5843H77.5448ZM77.0335 52.662C82.9647 52.662 85.9303 50.5997 85.9303 46.4751C85.9303 44.3276 85.1633 42.7255 83.6294 41.6688C82.0955 40.6121 80.0673 40.0838 77.5448 40.0838H70.591C70.2843 40.0838 70.0627 40.1349 69.9263 40.2372C69.8241 40.3394 69.7729 40.5099 69.7729 40.7485V51.895C69.7729 52.4063 69.9604 52.662 70.3354 52.662H77.0335ZM142.707 56.8624C142.81 56.9647 142.997 57.0158 143.27 57.0158H163.876C164.387 57.0158 164.643 56.7772 164.643 56.3V53.948V53.3344H163.978H149.866C149.593 53.3344 149.457 53.2492 149.457 53.0788C149.457 52.9765 149.508 52.8572 149.61 52.7208L163.876 33.8536C164.251 33.2741 164.438 32.7628 164.438 32.3197V30.479V29.9144C164.438 29.9144 164.051 29.9165 163.876 29.9165H144.241C143.866 29.9165 143.679 30.121 143.679 30.5301V32.831C143.679 33.1037 143.713 33.2911 143.781 33.3934C143.883 33.4957 144.071 33.5468 144.344 33.5468H157.075C157.382 33.5468 157.535 33.632 157.535 33.8025L157.382 34.1092L143.219 52.9765C142.946 53.3515 142.759 53.6412 142.656 53.8457C142.588 54.0502 142.554 54.3059 142.554 54.6127V56.3C142.554 56.5727 142.605 56.7602 142.707 56.8624ZM116.929 19.0676H111.51V27.7684C114.503 27.7684 116.929 25.3419 116.929 22.3486V19.0676ZM116.926 56.0308C116.926 56.4058 116.841 56.6614 116.67 56.7978C116.534 56.9341 116.278 57.0023 115.903 57.0023H112.427C112.086 57.0023 111.847 56.9512 111.711 56.8489C111.574 56.7126 111.506 56.491 111.506 56.1842V30.6699C111.506 30.3972 111.557 30.2098 111.66 30.1075C111.762 29.9712 111.949 29.903 112.222 29.903H117.028L116.926 56.0308ZM132.183 34.3137C132.183 33.9728 132.336 33.8024 132.643 33.8024H138.779C139.256 33.8024 139.495 33.5979 139.495 33.1888V30.4789V29.9165H138.881H132.745C132.439 29.9165 132.285 29.7631 132.285 29.4563V21.531V20.713L131.621 20.7129H128.093C127.752 20.7129 127.547 20.9515 127.479 21.4288L126.865 29.4563C126.865 29.7631 126.729 29.9165 126.456 29.9165H122.366C121.957 29.9165 121.752 30.1039 121.752 30.4789V33.1888C121.752 33.5979 121.974 33.8024 122.417 33.8024H126.252C126.593 33.8024 126.763 34.0069 126.763 34.416V50.6244C126.763 52.806 127.309 54.4252 128.399 55.4819C129.49 56.5045 131.16 57.0158 133.41 57.0158C135.796 57.0158 137.535 56.9306 138.625 56.7601C139.137 56.6579 139.392 56.3681 139.392 55.8909V53.6923V53.0787H138.779H135.507C134.348 53.0787 133.495 52.806 132.95 52.2606C132.439 51.7152 132.183 50.7267 132.183 49.295V34.3137Z" />
<path d="M0.241243 33.2639H10.9742C15.0585 33.2639 18.9054 35.1835 21.3612 38.4471L31.9483 52.5165C32.1484 52.7824 32.1786 53.1393 32.026 53.435L25.9232 65.2592C25.6304 65.8265 24.8455 65.8932 24.4612 65.3835L0.241243 33.2639Z" />
<path d="M42.4727 33.2822H31.7398C27.6555 33.2822 23.8086 31.3626 21.3528 28.0991L10.7656 14.0297C10.5656 13.7638 10.5354 13.4068 10.688 13.1111L16.7908 1.28696C17.0836 0.719654 17.8684 0.652924 18.2528 1.16266L42.4727 33.2822Z" />
</svg>
)
}
export default Logo

View File

@@ -0,0 +1,16 @@
import Link from "next/link"
const NavLink = ({className = "", href, children, ...props}) => {
return (
<Link href={href}>
<a
className={`block relative py-2 -mx-3 px-3 font-secondary rounded-md hover:bg-purple-light dark:hover:bg-purple-off-black hover:text-white ${className}`}
{...props}
>
{children}
</a>
</Link>
)
}
export {NavLink}

View File

@@ -0,0 +1,15 @@
export function PageHeader({title, align}) {
if (!title) return null
return (
<div className="mb-5">
<div className="flex items-center">
<h1
className={`w-full text-3xl lg:text-4xl xl:text-5xl font-semibold text-black dark:text-dark-mode-text font-primary text-${align}`}
>
{title}
</h1>
</div>
</div>
)
}

View File

@@ -0,0 +1,89 @@
import {useCallback, useEffect, useRef, useState} from "react"
const DEFAULT_SCROLLBAR_THUMB_SIZE = 54
export default function Scrollbar(props) {
const {children, thumbHeight, className, thumbColor} = props
const elementRef = useRef(null)
const [scrollbarThumb, setScrollbarThumb] = useState(DEFAULT_SCROLLBAR_THUMB_SIZE)
const [displacement, setDisplacement] = useState(0)
const handleScroll = useCallback(() => {
const element = elementRef.current
if (element) {
const {offsetWidth, scrollWidth, scrollLeft} = element
let positionLeft =
(scrollLeft * (offsetWidth - scrollbarThumb)) /
(scrollWidth - scrollbarThumb - (offsetWidth - scrollbarThumb))
if (isNaN(positionLeft)) positionLeft = 0
positionLeft = Math.min(positionLeft, offsetWidth - scrollbarThumb)
setDisplacement(positionLeft)
}
}, [elementRef, scrollbarThumb])
const handleResize = useCallback(() => {
const element = elementRef.current
if (element) {
const {offsetWidth, scrollWidth} = element
const minScrollbarWidth = Math.min(
(offsetWidth / scrollWidth) * offsetWidth,
DEFAULT_SCROLLBAR_THUMB_SIZE,
)
setScrollbarThumb(minScrollbarWidth)
}
}, [elementRef])
useEffect(() => {
const element = elementRef.current
if (element) element.addEventListener("scroll", handleScroll)
window.addEventListener("resize", handleResize)
window.addEventListener("resize", handleScroll)
return () => {
if (element) element.removeEventListener("scroll", handleScroll)
window.removeEventListener("resize", handleResize)
window.removeEventListener("resize", handleScroll)
}
}, [elementRef, handleScroll, handleResize])
useEffect(() => {
handleScroll()
handleResize()
}, [handleScroll, handleResize])
const getThumbColor = (color) => {
if (color !== undefined) {
if (color === "white") {
return "white"
} else {
return "black"
}
}
}
return (
<div className="relative h-full">
<div ref={elementRef} className="hide-scrollbar relative overflow-x-auto h-full">
{children}
</div>
<div className={`w-full h-2 bottom-0 left-0 absolute rounded ${className}`}>
<hr className="text-blue-mid relative top-1/2 transform -translate-y-1/2" />
<div
className="bg-black dark:bg-white absolute opacity-100 rounded top-1/2 transform -translate-y-1/2"
style={{
backgroundColor: getThumbColor(thumbColor),
height: thumbHeight,
width: scrollbarThumb,
left: displacement,
}}
/>
</div>
</div>
)
}

View File

@@ -0,0 +1,121 @@
import {DocSearchModal, useDocSearchKeyboardEvents} from "@docsearch/react"
import Head from "next/head"
import Link from "next/link"
import {useRouter} from "next/router"
import {useCallback, useEffect, useRef, useState} from "react"
import {createPortal} from "react-dom"
import {BiSearch} from "react-icons/bi"
const ACTION_KEY_DEFAULT = ["Ctrl ", "Control"]
const ACTION_KEY_APPLE = ["⌘", "Command"]
function Hit({hit, children}) {
return (
<Link href={hit.url}>
<a>{children}</a>
</Link>
)
}
export function Search({className = ""}) {
const router = useRouter()
const [isOpen, setIsOpen] = useState(false)
const searchButtonRef = useRef()
const [initialQuery, setInitialQuery] = useState(null)
const [, setActionKey] = useState()
const onOpen = useCallback(() => {
setIsOpen(true)
}, [setIsOpen])
const onClose = useCallback(() => {
setIsOpen(false)
}, [setIsOpen])
const onInput = useCallback(
(e) => {
setIsOpen(true)
setInitialQuery(e.key)
},
[setIsOpen, setInitialQuery],
)
useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
onInput,
searchButtonRef,
})
useEffect(() => {
if (typeof navigator !== "undefined") {
if (/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) {
setActionKey(ACTION_KEY_APPLE)
} else {
setActionKey(ACTION_KEY_DEFAULT)
}
}
}, [])
return (
<>
<Head>
<link rel="preconnect" href="https://BH4D9OD16A-dsn.algolia.net" crossOrigin="true" />
</Head>
<button
type="button"
ref={searchButtonRef}
onClick={onOpen}
className={`py-2 px-3 rounded-md focus:outline-none focus:ring-inset focus:ring-white focus:ring-2 inline-block hover:bg-purple-light dark:hover:bg-purple-off-black hover:text-white ${className}`}
>
<BiSearch size="1.375rem" className="inline" />{" "}
<span className="hidden mx-1 text-base lg:inline">Search</span>
</button>
{isOpen &&
createPortal(
<DocSearchModal
initialQuery={initialQuery}
initialScrollY={window.scrollY}
onClose={onClose}
indexName="blitzjs"
apiKey="c4db860ae4162be48d4c867e33edcaa2"
appId="BH4D9OD16A"
navigator={{
navigate({itemUrl}) {
setIsOpen(false)
router.push(itemUrl)
},
}}
hitComponent={Hit}
transformItems={(items) => {
return items.map((item) => {
// We transform the absolute URL into a relative URL to
// leverage Next's preloading.
const a = document.createElement("a")
a.href = item.url
const hash = a.hash === "#content-wrapper" ? "" : a.hash
// The titles are encoded, so we need to decode them.
// &lt;Script&gt; --> <Script>
const _highlightResult = {...item._highlightResult}
_highlightResult.hierarchy.lvl0.value = _highlightResult.hierarchy.lvl0.value
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
return {
...item,
_highlightResult,
url: `${a.pathname}${hash}`,
}
})
}}
/>,
document.body,
)}
</>
)
}

View File

@@ -0,0 +1,21 @@
import Image from "next/image"
export function ShowcaseThumbnail({title, thumbnail, URL}) {
return (
<a href={URL} rel="noreferrer" target="_blank" className="overflow-visible">
<div className="showcase-block">
<Image
src={thumbnail}
className="showcase-block-image overflow-visible"
alt={title}
width={756}
height={402}
layout="responsive"
/>
<h4 className="showcase-block-title font-primary text-sm lg:text-md xl:text-lg text-gray-600 dark:text-gray-300 font-semibold my-1 mt-2">
{title}
</h4>
</div>
</a>
)
}

View File

@@ -0,0 +1,19 @@
import Image from "next/image"
export const SidebarTitle = ({title, iconPath, iconDarkPath}) => (
<div className="px-3 mb-5 flex">
{iconPath && (
<div className={`mr-4 ${iconDarkPath ? "dark:hidden" : ""}`}>
<Image src={iconPath} width="14" height="16" alt={title} />
</div>
)}
{iconDarkPath && (
<div className="mr-4 hidden dark:block ">
<Image src={iconDarkPath} width="14" height="18" alt={title} />
</div>
)}
<div className="text-sm uppercase tracking-wider text-purple-off-black dark:text-dark-mode-text font-normal font-primary">
{title}
</div>
</div>
)

View File

@@ -0,0 +1,10 @@
import Head from "next/head"
export const SocialCards = ({imageUrl}) => {
return (
<Head>
<meta key="twitter:image" name="twitter:image" content={"https://blitzjs.com" + imageUrl} />
<meta key="og:image" property="og:image" content={"https://blitzjs.com" + imageUrl} />
</Head>
)
}

View File

@@ -0,0 +1,178 @@
import {hierarchy, Pack} from "@visx/hierarchy"
import {ParentSize} from "@visx/responsive"
import clsx from "clsx"
import Image from "next/image"
import React from "react"
const sponsors = [
{
name: "Flightcontrol",
href: "https://www.flightcontrol.dev?ref=blitzjs",
imageUrl: "https://raw.githubusercontent.com/blitz-js/blitz/main/assets/flightcontrol.png",
tier: 1,
cost: 800,
},
{
name: "Fauna",
href: "https://dashboard.fauna.com/accounts/register?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020",
imageUrl: "https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/Fauna_Logo_Blue.png",
tier: 2,
cost: 500,
},
{
name: "RIT",
href: "https://rit-inc.co.jp/?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2021",
imageUrl: "https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/rit_logo.png",
tier: 3,
cost: 250,
},
{
name: "Boostry",
href: "https://boostry.co.jp/?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2021",
imageUrl: "https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/boostry.svg",
tier: 3,
cost: 250,
},
{
name: "Andreas",
href: "https://andreas.fyi/",
imageUrl: "https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/andreas.jpg",
tier: 4,
cost: 100,
},
{
name: "MeetKai",
href: "https://meetkai.com/?ref=blitzjs_web",
imageUrl: "https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/meetkai.png",
tier: 4,
cost: 100,
},
{
name: "JDLT",
href: "https://jdlt.co.uk/?ref=blitzjs_web",
imageUrl: "https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/jdlt.png",
tier: 4,
cost: 100,
},
]
const pack = {
children: sponsors,
name: "root",
radius: 0,
distance: 0,
}
export const SponsorPack = () => {
const root = React.useMemo(
() =>
hierarchy(pack)
.sum((d) => d?.cost * d?.cost)
.sort((a, b) => b.data.cost - a.data.cost),
[],
)
return (
<ParentSize>
{({width}) => {
return width < 10 ? null : (
<div
style={{
width,
height: width,
position: "relative",
}}
>
<style
dangerouslySetInnerHTML={{
__html: `
.spon-link {
transition: all .2s ease;
transform: translate(-50%, -50%);
}
.spon-link:hover {
z-index: 10;
transform: translate(-50%, -50%) scale(1.1);
}
.spon-link:hover .spon-tooltip {
opacity: 1;
}
`,
}}
/>
<Pack root={root} size={[width, width]} padding={width * 0.1}>
{(packData) => {
const circles = packData.descendants().slice(1) // skip first layer
return (
<div>
{[...circles].reverse().map((circle, i) => {
const tooltipX = circle.x > width / 2 ? "left" : "right"
const tooltipY = circle.y > width / 2 ? "top" : "bottom"
return (
<a
key={`circle-${i}`}
href={circle.data.href}
className="spon-link bg-off-white dark:bg-white absolute shadow-lg rounded-full z-0"
style={{
left: circle.x,
top: circle.y,
width: circle.r * 2,
height: circle.r * 2,
}}
target="_blank"
rel="noreferrer noopener"
>
<div
key={`circle-${i}`}
className="absolute bg-no-repeat bg-center bg-contain rounded-sm"
style={{
left: "50%",
top: "50%",
transform: "translate(-50%, -50%)",
width: circle.data.cost > 100 ? "80%" : "50%",
height: circle.data.cost > 100 ? "80%" : "50%",
}}
>
<Image
src={circle.data.imageUrl}
alt={circle.data.name}
layout="fill"
objectFit="contain"
/>
</div>
<div
className={clsx(
"spon-tooltip absolute",
"text-sm",
"bg-gray-900 text-white p-2 pointer-events-none",
"transform opacity-0",
"shadow-xl rounded-lg",
"flex flex-col items-center",
tooltipX === "left"
? `left-1/4 -translate-x-full`
: `right-1/4 translate-x-full`,
tooltipY === "top"
? `top-1/4 -translate-y-full`
: `bottom-1/4 translate-y-full`,
)}
>
<p className="whitespace-nowrap font-bold">{circle.data.name}</p>
{circle.data.name !== "Flightcontrol" && (
<p className="whitespace-nowrap">${circle.data.cost} / month</p>
)}
</div>
</a>
)
})}
</div>
)
}}
</Pack>
</div>
)
}}
</ParentSize>
)
}

View File

@@ -0,0 +1,13 @@
import Head from "next/head"
export function Title({children}) {
let title = children + (!children?.match(/blitz/i) ? ` - Blitz.js` : "")
return (
<Head>
<title key="title">{title}</title>
<meta key="twitter:title" name="twitter:title" content={title} />
<meta key="og:title" property="og:title" content={title} />
</Head>
)
}

View File

@@ -0,0 +1,44 @@
import clsx from "clsx"
import styles from "./Card.module.css"
/**
* @param {{type: 'caution' | 'info' | 'note', title: string, children: any}}
* @returns
*/
export function Card({type, title, children}) {
const defaultTitle = type[0].toUpperCase() + type.substr(1)
return (
<div
className={clsx(
styles.container,
type === "caution"
? "bg-[#fdea69]"
: type === "info"
? "bg-[#69c6fd]"
: type === "note"
? "bg-blue-primary"
: undefined,
)}
>
<h5 className={styles.heading}>
<span className={styles.icon}>
<InfoIcon />
</span>
{title || defaultTitle}
</h5>
<div className={styles.content}>{children}</div>
</div>
)
}
const InfoIcon = () => (
<svg width={15} height={15} viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx={7.5} cy={7.5} r={6.75} stroke="black" strokeWidth={1.5} strokeLinecap="round" />
<path
d="M6.81226 4.27344H8.18774V5.91699L7.83179 8.94043H7.177L6.81226 5.91699V4.27344ZM6.84302 9.45898H8.15259V10.729H6.84302V9.45898Z"
fill="black"
/>
</svg>
)

View File

@@ -0,0 +1,33 @@
.container {
@apply p-5 my-4 rounded-xl;
a {
@apply !text-purple-light !font-medium !no-underline hover:!underline;
}
a:hover {
text-decoration: underline !important;
}
}
.heading {
@apply mt-0 mb-4 capitalize font-bold flex items-center text-black;
}
.icon {
@apply inline-block align-middle mr-2;
svg {
@apply block w-4 h-4 stroke-0;
}
}
.content {
* {
@apply text-black;
}
strong {
color: inherit !important;
}
}

View File

@@ -0,0 +1,4 @@
.atApplyCodeWindow {
height: auto;
max-height: none;
}

View File

@@ -0,0 +1,15 @@
.container {
height: 23.125rem;
}
@screen lg {
.container {
height: 27.1875rem;
}
}
@screen xl {
.container {
height: 23.125rem;
}
}

View File

@@ -0,0 +1,17 @@
@screen sm {
.card {
height: 23.375rem;
}
}
@screen lg {
.card {
height: 27.5rem;
}
}
@screen xl {
.card {
height: 23.375rem;
}
}

View File

@@ -0,0 +1,10 @@
const Feature = ({title, children, className = ""}) => {
return (
<div className={`text-sm font-secondary ${className} max-w-[35rem]`}>
<h3 className="mb-5 text-2xl font-semibold lg:font-primary">{title}</h3>
<div className="mr-6 space-y-4 dark:text-blue-light">{children}</div>
</div>
)
}
export {Feature}

View File

@@ -0,0 +1,17 @@
import {Icon} from "@/components/home/Icon"
const FeatureIcon = ({icon, children, heading}) => {
return (
<div className="space-y-4">
<div className="flex items-center space-x-4">
<Icon name={icon} variant="dark" />
<h3 className="text-xl font-semibold">{heading}</h3>
</div>
<p className="text-left text-transparent bg-clip-text bg-gradient-to-r from-blue-gradient-white to-blue-gradient-light-blue">
{children}
</p>
</div>
)
}
export {FeatureIcon}

View File

@@ -0,0 +1,17 @@
import {Icon} from "@/components/home/Icon"
const FeatureIconTitle = ({icon, children, title}) => {
return (
<div className="xl:flex xl:flex-col xl:items-start xl:space-y-5">
<Icon name={icon} variant="dark" />
<h2 className="inline-block mt-0 mb-3 ml-2 text-lg font-semibold align-top xl:m-0 lg:align-bottom lg:text-xl">
{title}
</h2>
<p className="text-sm text-transparent font-secondary lg:mt-2 bg-clip-text bg-gradient-to-r from-blue-gradient-white to-blue-gradient-light-blue">
{children}
</p>
</div>
)
}
export {FeatureIconTitle}

View File

@@ -0,0 +1,12 @@
import {Icon} from "@/components/home/Icon"
const FeatureIcon = ({icon, children}) => {
return (
<div className="space-y-4">
<Icon name={icon} variant="dark" />
<p className="text-base lg:text-lg">{children}</p>
</div>
)
}
export {FeatureIcon}

View File

@@ -0,0 +1,112 @@
import Link from "next/link"
import clsx from "clsx"
import {IoLogoVercel} from "react-icons/io5"
import {Icon} from "@/components/home/Icon"
import {LinkList} from "@/components/home/LinkList"
import {NewsletterForm} from "@/components/home/NewsletterForm"
export function Footer({className, hasDarkMode}) {
return (
<footer className={className}>
<div className="border-t border-gray-300 dark:border-white border-opacity-50">
<div className="relative mx-auto max-w-7xl">
<a href="#top" className="absolute right-0 mr-2 -mt-5 xl:mt-24 xl:mr-6">
<Icon
name="arrowUp"
className="icon-expanded"
variant="custom"
customBackgroundClassName="text-purple-light dark:text-off-white"
customColorClassName="text-off-white dark:text-purple-off-black"
></Icon>
</a>
</div>
<div className="grid px-6 mx-auto max-w-7xl lg:grid-cols-3 gap-x-24 my-14 lg:mt-24 lg:mb-12 gap-y-7">
<div className="flex flex-col justify-between space-y-7">
<p className="text-lg font-semibold">
Want to receive the latest news and updates from the Blitz team? Sign up for our
newsletter!
</p>
<div className="pb-5 lg:pb-0">
<NewsletterForm hasDarkMode={hasDarkMode} />
</div>
</div>
<div className="flex flex-col justify-between space-y-7 lg:col-span-2">
<div className="grid gap-7 md:grid-cols-3">
<LinkList title="Docs">
<Link href="/docs">
<a>All Docs</a>
</Link>
<Link href="/docs/get-started">
<a>Get Started</a>
</Link>
<Link href="/docs/contributing">
<a>How To Contribute</a>
</Link>
</LinkList>
<LinkList title="Community">
<Link href="https://discord.blitzjs.com/">
<a target="_blank" rel="noopener noreferrer">
Discord
</a>
</Link>
<Link href="https://github.com/blitz-js/blitz/discussions">
<a target="_blank" rel="noopener noreferrer">
Forum Discussions
</a>
</Link>
<Link href="https://twitter.com/blitz_js">
<a target="_blank" rel="noopener noreferrer">
Twitter
</a>
</Link>
<Link href="/showcase">
<a>Showcase</a>
</Link>
</LinkList>
<LinkList title="Other">
<Link href="https://flightcontrol.dev?ref=blitzjs">
<a target="_blank" rel="noopener noreferrer">
Deploy with Flightcontrol
</a>
</Link>
<Link href="https://github.com/blitz-js/blitz">
<a target="_blank" rel="noopener noreferrer">
GitHub
</a>
</Link>
<Link href="https://github.com/blitz-js/blitz/wiki">
<a target="_blank" rel="noopener noreferrer">
Wiki
</a>
</Link>
<Link href="https://store.blitzjs.com">
<a target="_blank" rel="noopener noreferrer">
Swag
</a>
</Link>
</LinkList>
</div>
<div
className={clsx("text-xs font-secondary", {
"text-off-white": !hasDarkMode,
"dark:text-off-white text-black": hasDarkMode,
})}
>
<Link href="https://vercel.com/?utm_source=blitzjs">
<a target="_blank" rel="noopener noreferrer">
Hosted on <IoLogoVercel className="inline" /> Vercel
</a>
</Link>
<br />
Copyright &copy; {new Date().getFullYear()} Brandon Bayer and Blitz.js Contributors
</div>
</div>
</div>
</div>
</footer>
)
}

View File

@@ -0,0 +1,17 @@
@screen sm {
.nav {
grid-template-rows: repeat(3, auto) 1fr;
}
}
@screen md {
.nav {
grid-template-rows: repeat(3, auto);
}
}
@screen lg {
.nav {
grid-template-rows: auto 1fr;
}
}

View File

@@ -0,0 +1,314 @@
const Hand = ({className = "", style = "", variant = ""}) => {
style = {...style, maxWidth: "120rem"}
className += " absolute hand"
switch (variant) {
case "hero-rightarm":
return (
<svg
width="982"
height="208"
viewBox="0 0 982 208"
fill="none"
style={style}
className={className}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M118 186.266C57.1274 212.236 18.5522 173.216 13.0004 127.51C4.00037 -25.3613 278.166 18.2916 278.166 18.2916C350.492 30.4269 393.243 51.884 434.035 68.0809C474.827 84.2777 543.902 117.65 594.286 127.51C644.671 137.37 674 139 736.5 107.031C799 75.0629 834.5 41 981 30.5"
stroke="url(#hero-rightarm)"
strokeWidth="25"
/>
<path
d="M123.908 170.828C122.708 169.628 112.896 174.495 108.396 176.828L109.599 178.748C112.432 185.581 118.299 199.248 119.099 199.248C120.099 199.248 133.973 194.464 134.744 189.454C135.514 184.445 125.408 172.328 123.908 170.828Z"
fill="var(--color-stop)"
/>
<defs>
<linearGradient
id="hero-rightarm"
x1="731"
y1="13"
x2="964.461"
y2="13"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="var(--color-stop)" />
<stop offset="1" stopColor="var(--color-stop)" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
)
case "hero-righthand":
return (
<svg
width="73"
height="78"
viewBox="0 0 73 78"
fill="none"
style={style}
className={className}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M39.2996 17.7102C18.2108 38.0297 22.4787 31.541 12.079 39.3321C-7.13906 51.8432 18.5344 67.6414 25.7152 67.1347C28.5706 67.2746 38.4463 60.8317 42.2092 57.6385C45.2196 55.084 55.4957 50.9048 60.2575 49.1345C63.768 47.5891 62.4191 43.5731 58.8336 44.3133C58.0285 44.3884 55.6507 45.0161 55.6507 45.0161C53.9749 45.3347 48.2552 47.5733 50.0333 43.9967C51.8114 40.4201 64.5421 30.6252 66.4046 28.8272C69.1266 26.1994 65.7759 22.9573 63.2114 25.0643C60.6469 27.1713 54.3288 33.445 52.7933 34.4001C50.8273 35.9638 49.4692 34.4661 51.267 32.837C52.1659 32.0224 63.3516 22.209 65.111 20.177C67.6244 17.2742 63.8834 14.8501 61.1874 17.2944C59.3902 18.9237 48.9446 28.8304 47.7743 29.3456C45.4712 30.7783 44.7018 29.1214 46.0795 27.7169C47.0439 26.7338 55.3373 19.8699 57.0967 17.838C60.6155 13.7741 56.365 11.734 53.604 14.3466C53.604 14.3466 44.2253 23.5046 42.6899 24.4596C40.7239 26.0232 39.928 23.58 40.892 22.5969C41.8561 21.6138 44.4211 19.507 47.3132 16.5572C51.0663 12.3902 46.3851 10.9587 43.9236 13.2999C41.462 15.641 39.2996 17.7102 39.2996 17.7102Z"
fill="var(--color-stop)"
/>
</svg>
)
case "hero-leftarm":
return (
<svg
width="1558"
height="298"
viewBox="0 0 1558 298"
fill="none"
style={style}
className={className}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1509.43 12.3718C1508.28 13.6179 1513.52 23.2312 1516.03 27.6361L1517.9 26.3589C1524.62 23.2597 1538.05 16.8613 1538.02 16.0619C1537.98 15.0627 1532.65 1.38682 1527.62 0.813304C1522.58 0.239789 1510.87 10.8141 1509.43 12.3718Z"
fill="var(--color-stop)"
/>
<path
d="M1527.54 20C1540.45 44.6976 1559.35 98.008 1527.54 136.79C1480.03 194.731 1380.34 219.066 1307.26 190.965C1237.79 159.126 1263.41 139.147 1064.97 52.7328C866.522 -33.6809 821.336 221.064 588.418 150.135C434.185 103.167 435.624 119.166 316.836 190.965C198.048 262.763 209.228 275.01 1 285"
stroke="url(#hero-leftarm)"
strokeWidth="25"
/>
<defs>
<linearGradient
id="hero-leftarm"
x1="21.0309"
y1="20.2654"
x2="234.129"
y2="20.1485"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="var(--color-stop)" stopOpacity="0" />
<stop offset="1" stopColor="var(--color-stop)" />
</linearGradient>
</defs>
</svg>
)
case "hero-lefthand":
return (
<svg
width="63"
height="56"
viewBox="0 0 63 56"
fill="none"
style={style}
className={className}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M28.3105 6.17507C49.3993 26.4945 45.1314 20.0059 55.5312 27.797C74.7492 40.3081 49.0758 56.1063 41.8949 55.5995C39.0395 55.7394 29.1638 49.2965 25.4009 46.1034C22.3905 43.5489 12.1144 39.3696 7.35258 37.5993C3.84212 36.054 5.19099 32.0379 8.7765 32.7782C9.58161 32.8532 11.9594 33.4809 11.9594 33.4809C13.6352 33.7995 19.3549 36.0381 17.5768 32.4615C15.7987 28.8849 3.06797 19.09 1.20554 17.292C-1.51647 14.6643 1.83416 11.4221 4.39867 13.5291C6.96318 15.6362 13.2814 21.9099 14.8168 22.865C16.7828 24.4287 18.1409 22.9309 16.3431 21.3018C15.4442 20.4872 4.25849 10.6738 2.4991 8.64182C-0.0143295 5.73905 3.72669 3.31492 6.42269 5.75923C8.21987 7.3885 18.6655 17.2953 19.8358 17.8104C22.1389 19.2432 22.9083 17.5862 21.5306 16.1818C20.5662 15.1986 12.2728 8.33475 10.5134 6.30282C6.9946 2.23891 11.2451 0.19889 14.0061 2.81145C14.0061 2.81145 23.3848 11.9694 24.9202 12.9244C26.8862 14.488 27.6822 12.0448 26.7181 11.0617C25.754 10.0786 23.189 7.97183 20.2969 5.02205C16.5438 0.855054 21.225 -0.576457 23.6865 1.76471C26.1481 4.10588 28.3105 6.17507 28.3105 6.17507Z"
fill="var(--color-stop)"
/>
</svg>
)
case "concepts-right":
return (
<svg
width="945"
height="202"
viewBox="0 0 945 202"
fill="none"
xmlns="http://www.w3.org/2000/svg"
style={style}
className={className}
>
<path
d="M28.044 98.5362C30.9131 100.099 41.6149 104.007 45.1834 104.605C57.6823 106.698 61.9164 132.109 49.8005 134.292L22.1683 138.449C16.379 138.815 14.973 137.685 14.4633 135.219C13.9536 132.753 15.4409 132.164 16.5863 131.861C17.7317 131.558 24.0936 131.006 23.8427 129.963C23.6468 129.148 22.101 129.729 19.8433 130.196C17.4729 130.686 10.9859 132.713 8.66418 131.339C6.34243 129.966 6.21071 126.329 8.91392 125.805C11.6171 125.281 16.5169 124.887 16.5169 124.887C18.6105 124.594 18.0852 122.767 16.023 123.055C16.023 123.055 11.1366 123.834 9.49261 123.969C7.84861 124.104 5.14852 124.904 4.67485 121.519C4.20118 118.135 8.30112 118.197 9.23311 118.067C10.1651 117.936 13.633 117.593 14.4988 117.33C15.3647 117.066 15.0021 115.834 14.2901 115.838C13.5781 115.843 11.4031 116.049 8.67302 116.188C6.79283 116.285 5.70429 115.591 5.54028 113.069C5.37627 110.546 6.39422 108.96 12.4562 109.185C17.6624 109.539 21.0714 109.75 21.7741 108.251C22.3253 107.075 19.9195 104.515 18.6305 102.778C16.4823 100.228 15.256 96.6248 15.2092 92.6994C15.4875 88.42 19.6254 88.0129 20.65 92.3951C22.1883 96.2029 25.1749 96.9729 28.044 98.5362Z"
fill="var(--color-stop)"
/>
<path
d="M510.275 69.3963L520.526 76.5483L520.545 76.5223L520.563 76.4963L510.275 69.3963ZM473.37 124.365L462.754 117.766L473.37 124.365ZM274.006 104.417L286.492 103.822L274.006 104.417ZM288.849 63.966L296.595 73.777L288.849 63.966ZM326.006 133.508L328.511 145.754L326.006 133.508ZM261.143 128.244L257.08 140.065L261.143 128.244ZM214.043 104.417L217.996 92.5581L214.043 104.417ZM149.211 101.424L146.376 89.2502L149.211 101.424ZM77.6896 109.969C70.9629 111.521 66.7681 118.232 68.3205 124.959C69.8728 131.686 76.5843 135.881 83.3111 134.328L77.6896 109.969ZM510.275 69.3963C500.023 62.2443 500.023 62.2446 500.022 62.245C500.022 62.2453 500.022 62.2459 500.021 62.2465C500.021 62.2478 500.019 62.2496 500.018 62.252C500.014 62.2567 500.009 62.2636 500.003 62.2727C499.991 62.2908 499.972 62.3175 499.947 62.3525C499.899 62.4227 499.826 62.5264 499.732 62.6621C499.543 62.9334 499.265 63.3324 498.908 63.8463C498.194 64.8741 497.162 66.3615 495.887 68.2049C493.339 71.8908 489.816 77.0048 485.922 82.7171C478.174 94.0818 468.812 108.02 462.754 117.766L483.986 130.964C489.735 121.717 498.825 108.171 506.578 96.7997C510.434 91.1439 513.924 86.0768 516.45 82.4238C517.713 80.5977 518.734 79.126 519.438 78.1122C519.791 77.6053 520.064 77.2129 520.248 76.9479C520.34 76.8154 520.41 76.7148 520.457 76.6477C520.481 76.6141 520.498 76.5889 520.51 76.5723C520.516 76.564 520.52 76.5578 520.523 76.5538C520.524 76.5518 520.525 76.5503 520.526 76.5494C520.526 76.549 520.526 76.5487 520.526 76.5485C520.526 76.5483 520.526 76.5483 510.275 69.3963ZM462.754 117.766C450.596 137.324 437.187 151.937 421.701 161.693C406.34 171.37 388.263 176.697 365.982 176.697V201.697C392.543 201.697 415.322 195.259 435.027 182.846C454.606 170.511 470.47 152.708 483.986 130.964L462.754 117.766ZM365.982 176.697C350.442 176.697 330.789 167.853 314.356 153.127C297.919 138.397 287.267 120.095 286.492 103.822L261.52 105.011C262.73 130.417 278.434 154.505 297.672 171.745C316.916 188.99 342.258 201.697 365.982 201.697V176.697ZM286.492 103.822C286.1 95.6047 286.179 90.4197 287.439 86.0712C288.527 82.3191 290.755 78.3872 296.595 73.777L281.103 54.155C271.734 61.5518 266.153 69.7084 263.428 79.1114C260.875 87.9181 261.136 96.9549 261.52 105.011L286.492 103.822ZM296.595 73.777C300.989 70.308 310.95 68.9404 322.61 72.9575C334.206 76.9526 341.16 84.0946 342.472 90.0419L366.885 84.6576C362.869 66.4486 346.13 54.6188 330.754 49.321C315.441 44.0453 295.152 43.064 281.103 54.155L296.595 73.777ZM342.472 90.0419C344.672 100.021 343.354 106.778 340.699 111.128C338.124 115.348 333.069 119.304 323.501 121.261L328.511 145.754C343.02 142.786 354.966 135.741 362.039 124.153C369.033 112.694 370.012 98.835 366.885 84.6576L342.472 90.0419ZM323.501 121.261C306.91 124.655 282.165 122.252 265.207 116.423L257.08 140.065C277.501 147.085 306.818 150.191 328.511 145.754L323.501 121.261ZM265.207 116.423C258.484 114.112 252.083 110.306 244.218 105.607C236.762 101.153 227.95 95.8763 217.996 92.5581L210.09 116.275C217.066 118.6 223.624 122.426 231.397 127.069C238.759 131.467 247.443 136.752 257.08 140.065L265.207 116.423ZM217.996 92.5581C196.066 85.2481 168.978 83.9867 146.376 89.2502L152.046 113.599C170.159 109.381 192.548 110.428 210.09 116.275L217.996 92.5581ZM146.376 89.2502C133.975 92.1381 122.055 96.1774 110.819 99.9704C99.3917 103.828 88.6501 107.439 77.6896 109.969L83.3111 134.328C95.673 131.476 107.574 127.452 118.816 123.657C130.249 119.797 141.023 116.166 152.046 113.599L146.376 89.2502ZM520.563 76.4963C536.992 52.69 566.993 39.4283 597.37 32.3532C627.322 25.3771 655.077 25.0039 664 25.0039L664 0.00390625C654.423 0.00390625 624.428 0.382176 591.699 8.0049C559.395 15.5287 521.783 30.7132 499.987 62.2964L520.563 76.4963ZM664 25.0039C682.549 25.0039 827.463 25.0122 942.262 36.4385L944.738 11.5615C828.537 -0.00439295 682.451 0.00390625 664 0.00390625L664 25.0039Z"
fill="url(#concepts-right)"
/>
<defs>
<linearGradient
id="concepts-right"
x1="587.5"
y1="43.9843"
x2="844"
y2="43.9843"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="var(--color-stop)" />
<stop offset="1" stopColor="var(--color-stop)" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
)
case "features-right":
return (
<svg
width="768"
height="388"
viewBox="0 0 768 388"
fill="none"
className={className}
style={style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M107.169 220.503C134.669 206.503 146.802 191.184 151.421 186.503C188.701 148.731 222.729 143.438 257.866 170.587C297.356 201.102 280.878 260.698 326.783 290.052C372.689 319.407 443.487 293.686 435.244 212.639C413.085 55.1971 524.833 55.0565 587.129 70.0002C657.587 86.9017 694.5 147.584 748.5 170.586"
stroke="url(#features-right)"
strokeWidth="25"
/>
<path
d="M87.7811 217.167C87.5841 215.481 101.124 209.663 104.798 207.742L113.762 231.089C111.933 231.683 98.081 237.642 93.6737 235.14C89.2665 232.637 88.0273 219.274 87.7811 217.167Z"
fill="var(--color-stop)"
/>
<path
d="M31.1139 243.353C60.3046 241.004 52.8908 243.317 65.8729 243.883C88.6674 246.387 78.8234 217.895 72.9481 213.735C70.8282 211.817 59.1059 210.541 54.1715 210.626C50.2239 210.693 39.6267 207.412 34.8215 205.764C31.1268 204.734 29.6246 208.695 32.8671 210.395C33.5374 210.847 35.7744 211.869 35.7744 211.869C37.2725 212.685 43.1154 214.579 39.4731 216.218C35.8308 217.857 19.7756 217.364 17.1955 217.574C13.4245 217.882 13.9611 222.513 17.2802 222.509C20.5993 222.505 29.4627 221.657 31.2557 221.891C33.7674 221.928 33.8685 223.947 31.4454 224.068C30.2339 224.128 15.3619 224.628 12.7132 225.085C8.92923 225.737 10.2863 229.983 13.9208 229.801C16.3436 229.68 30.7022 228.639 31.9339 228.982C34.6234 229.334 34.1683 231.103 32.2125 231.316C30.8434 231.465 20.0781 231.517 17.4294 231.974C12.1319 232.887 14.1264 237.159 17.9179 236.888C17.9179 236.888 30.9767 235.749 32.7696 235.984C35.2813 236.021 34.3483 238.415 32.9796 238.564C31.6108 238.713 28.2914 238.717 24.1848 239.166C18.6407 240.01 21.3541 244.084 24.742 243.834C28.1298 243.583 31.1139 243.353 31.1139 243.353Z"
fill="var(--color-stop)"
/>
<defs>
<linearGradient
id="features-right"
x1="499.5"
y1="81.4962"
x2="749.5"
y2="81.4962"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="var(--color-stop)" />
<stop offset="1" stopColor="var(--color-stop)" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
)
case "sandbox-right":
return (
<svg
width="732"
height="731"
viewBox="0 0 732 731"
fill="none"
className={className}
style={style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M204.705 140.991C182.134 146.765 114.159 100.534 73.8538 163.466C73.8538 163.466 7.37995 264.93 143.646 317.746C220.39 347.491 319.729 339.02 329.694 391.243C336.176 425.209 312.155 442.017 287.406 433.532C271.297 428.009 253.131 412.677 256.54 384.303C266.942 320.028 292.812 220.757 423.003 257.825C536.003 278.825 531.002 401.008 604.501 471.505C678.001 542.001 692.5 542.001 692.5 542.001"
stroke="url(#sandbox-right)"
strokeWidth="25"
/>
<defs>
<linearGradient
id="sandbox-right"
x1="614"
y1="593.5"
x2="441.5"
y2="432"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="var(--color-stop)" stopOpacity="0" />
<stop offset="1" stopColor="var(--color-stop)" />
</linearGradient>
</defs>
</svg>
)
case "sponsors-left":
return (
<svg
width="830"
height="685"
viewBox="0 0 830 685"
fill="none"
className={className}
style={style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M353 134.498L362.708 142.372L362.708 142.372L353 134.498ZM328.227 653.572C334.618 650.962 337.683 643.664 335.072 637.273C332.462 630.882 325.165 627.818 318.774 630.428L328.227 653.572ZM829.717 119.795C792.387 66.8444 714.704 15.0256 624.616 3.00717C533.569 -9.1393 430.254 19.4189 343.293 126.623L362.708 142.372C444.147 41.9765 538.832 16.7844 621.31 27.7876C704.747 38.9189 776.114 87.1498 809.284 134.2L829.717 119.795ZM343.293 126.623C307.677 170.53 295.237 208.666 283.724 244.833C272.327 280.637 261.916 314.168 231.222 352.713L250.779 368.287C284.585 325.832 296.103 288.363 307.546 252.416C318.873 216.833 330.184 182.468 362.708 142.372L343.293 126.623ZM231.222 352.713C201.358 390.216 143.909 418.741 95.203 428.362C70.7385 433.194 50.653 432.802 38.2834 428.121C32.3436 425.874 29.0008 422.962 27.234 419.966C25.5218 417.063 24.3985 412.578 25.7789 405.343L1.22184 400.657C-1.02274 412.422 0.228941 423.39 5.70035 432.667C11.1171 441.851 19.7665 447.845 29.4361 451.504C48.2852 458.636 73.8872 458.056 100.048 452.888C152.591 442.509 216.142 411.784 250.779 368.287L231.222 352.713ZM25.7789 405.343C31.4791 375.465 60.0694 361.166 86.253 366.784C99.0003 369.519 110.62 376.995 118.044 389.59C125.486 402.214 129.422 421.232 124.227 448.13L148.774 452.87C154.829 421.518 150.827 395.973 139.581 376.895C128.318 357.786 110.5 346.418 91.4978 342.341C54.1813 334.334 10.0216 354.535 1.22184 400.657L25.7789 405.343ZM124.227 448.13C118.647 477.024 111.844 510.588 108.836 542.677C105.852 574.516 106.377 606.803 116.778 632.664C122.069 645.82 130.005 657.542 141.469 666.556C152.944 675.58 167.303 681.379 184.642 683.673C218.832 688.197 265.445 679.215 328.227 653.572L318.774 630.428C257.306 655.535 215.606 662.553 187.921 658.889C174.323 657.09 164.354 652.748 156.922 646.905C149.48 641.052 143.932 633.18 139.973 623.336C131.873 603.197 130.836 575.859 133.727 545.01C136.595 514.412 143.104 482.226 148.774 452.87L124.227 448.13Z"
fill="var(--color-stop)"
/>
<path
d="M397.361 643.048C368.083 643.71 375.695 645.25 362.84 647.146C340.423 651.979 347.288 622.626 352.705 617.885C354.617 615.759 366.146 613.286 371.063 612.863C374.996 612.525 385.201 608.173 389.811 606.039C393.38 604.635 395.282 608.421 392.231 610.445C391.611 610.964 389.49 612.21 389.49 612.21C388.084 613.175 382.467 615.659 386.258 616.916C390.049 618.172 405.969 616.032 408.557 615.976C412.34 615.894 412.282 620.556 408.98 620.893C405.678 621.23 396.774 621.297 395.015 621.715C392.52 622.009 392.627 624.028 395.05 623.899C396.261 623.835 411.106 622.804 413.787 622.986C417.618 623.246 416.705 627.609 413.071 627.802C410.648 627.931 396.258 628.37 395.069 628.838C392.429 629.464 393.064 631.177 395.031 631.188C396.408 631.195 407.122 630.141 409.804 630.323C415.167 630.687 413.622 635.142 409.823 635.262C409.823 635.262 396.716 635.471 394.957 635.888C392.462 636.183 393.636 638.468 395.013 638.476C396.39 638.484 399.692 638.147 403.823 638.171C409.424 638.441 407.144 642.773 403.748 642.872C400.353 642.97 397.361 643.048 397.361 643.048Z"
fill="var(--color-stop)"
stroke="var(--color-stop)"
/>
<rect x="791.5" y="113" width="38" height="22" fill="var(--page-bg-color)" />
</svg>
)
case "hero-squiggle":
return (
<svg
width="467"
height="321"
viewBox="0 0 467 321"
fill="none"
className={className}
style={style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M467 22.1987C440.705 22.1983 390.219 -3.92313 323.955 31.8721C301.868 45.8998 282.935 60.8963 272.418 71.0544C261.9 81.2125 242.967 107.334 242.967 107.334C242.967 107.334 221.405 136.325 186.302 159.169C151.198 182.014 116.538 191.113 82.9877 201.949C49.4368 212.785 16.2329 225.362 13.1499 259.706C8.41683 312.432 117 338.5 163.031 251.483C183.388 213 272.418 213 272.418 213"
stroke="var(--color-stop)"
strokeWidth="25"
/>
</svg>
)
case "community-squiggle":
return (
<svg
width="200"
height="550"
viewBox="0 0 200 550"
fill="none"
className={className}
style={style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M43.4336 122.125L49.3197 111.097L49.1666 111.016L49.0113 110.938L43.4336 122.125ZM132 167L127.289 178.579L132 167ZM117.871 349.079L115.175 336.873L117.871 349.079ZM75.4939 341.284L83.8552 331.992L75.4939 341.284ZM137.757 292.912L149.405 288.374L137.757 292.912ZM143.529 357.731L155.867 359.739L143.529 357.731ZM128.002 408.18L115.646 406.288L128.002 408.18ZM136.007 472.586L124.487 477.437L136.007 472.586ZM156.513 541.635C159.18 548.003 166.503 551.003 172.871 548.337C179.239 545.67 182.239 538.346 179.573 531.979L156.513 541.635ZM37.5474 133.152C94.1785 163.38 105.816 169.842 127.289 178.579L136.711 155.422C116.755 147.303 106.556 141.648 49.3197 111.097L37.5474 133.152ZM127.289 178.579C149.555 187.637 157.58 193.646 162.125 200.965C164.524 204.828 166.391 209.781 168.165 217.221C169.966 224.776 171.467 233.938 173.571 246.213L198.212 241.989C196.19 230.194 194.522 219.975 192.483 211.423C190.417 202.757 187.774 194.88 183.363 187.776C174.287 173.161 159.492 164.69 136.711 155.422L127.289 178.579ZM173.571 246.213C176.197 261.529 170.801 282.395 159.064 301.079C147.323 319.768 131.083 333.36 115.175 336.873L120.567 361.285C145.403 355.8 166.491 336.252 180.233 314.377C193.978 292.497 202.22 265.372 198.212 241.989L173.571 246.213ZM115.175 336.873C107.142 338.648 102.018 339.446 97.5195 338.939C93.6376 338.501 89.3858 336.969 83.8552 331.992L67.1326 350.576C76.0062 358.561 84.9884 362.684 94.7168 363.781C103.828 364.809 112.691 363.025 120.567 361.285L115.175 336.873ZM83.8552 331.992C79.6935 328.248 76.6624 318.66 78.6518 306.49C80.6302 294.386 86.4946 286.325 92.1347 284.027L82.7029 260.874C65.4343 267.909 56.6027 286.406 53.9792 302.457C51.3666 318.441 53.8274 338.604 67.1326 350.576L83.8552 331.992ZM92.1347 284.027C101.598 280.172 108.481 280.329 113.217 282.211C117.812 284.036 122.565 288.35 126.11 297.45L149.405 288.374C144.028 274.574 135.066 263.991 122.449 258.978C109.973 254.02 96.1482 255.397 82.7029 260.874L92.1347 284.027ZM126.11 297.45C132.258 313.229 134.071 338.024 131.191 355.724L155.867 359.739C159.335 338.425 157.443 309.005 149.405 288.374L126.11 297.45ZM131.191 355.724C130.049 362.74 127.379 369.692 124.077 378.238C120.947 386.339 117.235 395.916 115.646 406.288L140.358 410.073C141.471 402.805 144.134 395.694 147.397 387.249C150.488 379.249 154.23 369.797 155.867 359.739L131.191 355.724ZM115.646 406.288C112.147 429.138 115.48 456.049 124.487 477.437L147.527 467.734C140.31 450.595 137.559 428.351 140.358 410.073L115.646 406.288ZM124.487 477.437C129.429 489.172 135.424 500.238 141.061 510.671C146.794 521.283 152.168 531.26 156.513 541.635L179.573 531.979C174.672 520.276 168.696 509.226 163.056 498.788C157.32 488.171 151.92 478.166 147.527 467.734L124.487 477.437ZM49.0113 110.938C22.9675 97.9524 18.7103 69.6275 34.4616 48.8001C49.8294 28.4797 88.1146 12.3355 152.232 38.7931L161.768 15.6833C91.3854 -13.3596 39.1705 1.12793 14.5218 33.7201C-9.74353 65.8054 -3.53395 112.674 37.8559 133.311L49.0113 110.938Z"
fill="var(--color-stop)"
/>
</svg>
)
case "sponsors-squiggle":
return (
<svg
width="407"
height="158"
viewBox="0 0 407 158"
fill="none"
className={className}
style={style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M401 79.5008C373.59 93.6762 361.556 109.073 356.966 113.783C319.929 151.793 254 156.815 201.625 116.877C161.941 86.6153 167.531 54.008 121.438 24.9472C79.1766 -1.69862 12.8758 17.9975 12.8758 83.9971"
stroke="var(--color-stop)"
strokeWidth="25"
/>
</svg>
)
case "features-squiggle":
return (
<svg
width="314"
height="131"
viewBox="0 0 314 131"
fill="none"
className={className}
style={style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M302 127C293.035 98.6904 209.437 106.29 186.5 106.29C177.163 106.29 107.5 106.29 93.5 105C79.5 103.71 66.7231 98.0329 57.0321 91.654C39.058 79.823 26.6344 61.1728 21.532 40.4797C20.0226 34.358 19.7579 23.7548 15.8749 18.5775C13.1937 15.0027 10.6383 13.4256 6.99982 11"
stroke="var(--color-stop)"
strokeWidth="25"
/>
</svg>
)
default:
throw new Error(`Invalid variant, ${variant}`)
}
}
export {Hand}

View File

@@ -0,0 +1,109 @@
import {useState} from "react"
import {CodeWindow} from "@/components/CodeWindow"
import {useIsDesktop} from "@/hooks/useIsDesktop"
import tokenize from "../../macros/tokenize.macro.js"
const pageTokenized = tokenize.jsx(
`//---- ON THE CLIENT ----
// app/pages/projects/new.tsx
import { BlitzPage, Routes } from "@blitzjs/next"
import { useRouter } from "next/router"
import { useMutation } from "@blitzjs/rpc"
import Layout from "app/core/layouts/Layout"
// Notice how we import the server function directly
import createProject, { CreateProject } from "app/projects/mutations/createProject"
import { ProjectForm } from "app/projects/components/ProjectForm"
const NewProjectPage: BlitzPage = () => {
const router = useRouter()
const [createProjectMutation] = useMutation(createProject)
return (
<div>
<h1>Create New Project</h1>
<ProjectForm
submitText="Create Project"
schema={CreateProject}
onSubmit={async (values) => {
// This is equivalent to calling the server function directly
const project = await createProjectMutation(values)
// Notice the 'Routes' object Blitz provides for routing
router.push(Routes.ProjectsPage({ projectId: project.id }))
}}
/>
</div>
);
};
NewProjectPage.authenticate = true
NewProjectPage.getLayout = (page) => <Layout>{page}</Layout>
export default NewProjectPage
`,
true,
)
const mutationTokenized = tokenize.jsx(
`// ---- ON THE SERVER ----
// app/projects/mutations/createProject.ts
import { resolver } from "@blitzjs/rpc"
import db from "db"
import * as z from "zod"
// This provides runtime validation + type safety
export const CreateProject = z
.object({
name: z.string(),
})
// resolver.pipe is a functional pipe
export default resolver.pipe(
// Validate the input data
resolver.zod(CreateProject),
// Ensure user is logged in
resolver.authorize(),
// Perform business logic
async (input) => {
const project = await db.project.create({ data: input })
return project
}
)`,
true,
)
const HeroCode = ({className = ""}) => {
const isDesktop = useIsDesktop()
const [tabs, setTabs] = useState([
{
title: isDesktop ? "mutations/createProject.ts" : "createProject.ts",
tokens: mutationTokenized.tokens,
selected: true,
},
{
title: "pages/projects/new.tsx",
tokens: pageTokenized.tokens,
selected: false,
},
])
return (
<CodeWindow
className={className}
tabs={tabs}
onTabClick={(tabIndex) => {
setTabs(
tabs.map((tab, i) => ({
...tab,
selected: i === tabIndex,
})),
)
}}
>
<CodeWindow.Code tokens={tabs.find((tab) => tab.selected).tokens} />
</CodeWindow>
)
}
export {HeroCode}

View File

@@ -0,0 +1,243 @@
import {AiFillDatabase} from "react-icons/ai"
import {BsArrowUp, BsBarChartFill} from "react-icons/bs"
import {FaCircle, FaPlug} from "react-icons/fa"
import {FaMedal} from "react-icons/fa"
import {HiLightningBolt} from "react-icons/hi"
import {IoMdCube, IoMdThumbsUp} from "react-icons/io"
import {IoClose, IoLayers} from "react-icons/io5"
import {RiFileCodeFill} from "react-icons/ri"
import {SiTypescript} from "react-icons/si"
const Icon = ({
name,
className = "",
variant = "light",
customBackgroundClassName = "",
customColorClassName = "",
}) => {
let iconClassName = `icon inline col-start-1 row-start-1 ${
variant === "custom" ? customColorClassName : "text-purple-light dark:text-purple-primary"
}`
let reactIcon
switch (name) {
case "lighteningBolt":
reactIcon = <HiLightningBolt className={iconClassName} />
break
case "layers":
reactIcon = <IoLayers className={iconClassName} />
break
case "graphUp":
reactIcon = <BsBarChartFill className={iconClassName} />
break
case "thumbsUp":
reactIcon = <IoMdThumbsUp className={iconClassName} />
break
case "database":
reactIcon = <AiFillDatabase className={iconClassName} />
break
case "fileCode":
reactIcon = <RiFileCodeFill className={iconClassName} />
break
case "plugin":
reactIcon = <FaPlug className={iconClassName} />
break
case "typescript":
reactIcon = <SiTypescript className={iconClassName} />
break
case "scaffolding":
reactIcon = <IoMdCube className={iconClassName} />
break
case "medal":
reactIcon = <FaMedal className={iconClassName} />
break
case "arrowUp":
reactIcon = <BsArrowUp className={`icon-large ${iconClassName}`} />
break
case "modalClose":
reactIcon = <IoClose className={iconClassName} />
break
case "diamond-sponsor":
reactIcon = (
<svg
className={`icon-large ${iconClassName}`}
width="16"
height="22"
viewBox="0 0 16 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.20835 12.7464C4.30403 12.4873 4.59158 12.3549 4.85062 12.4506L8.5852 13.83C8.84423 13.9257 8.97666 14.2133 8.88098 14.4723L6.30282 21.4521C6.16389 21.8282 5.66094 21.896 5.42738 21.5701L4.56214 20.3627C4.45154 20.2083 4.26306 20.13 4.07563 20.1604L2.14978 20.4729C1.7718 20.5342 1.46799 20.1653 1.60067 19.8061L4.20835 12.7464Z"
fill="#6700EB"
/>
<path
d="M11.9242 12.7464C11.8285 12.4873 11.541 12.3549 11.282 12.4506L7.54737 13.83C7.28834 13.9257 7.15591 14.2133 7.25159 14.4723L9.82975 21.4521C9.96868 21.8282 10.4716 21.896 10.7052 21.5701L11.5704 20.3627C11.681 20.2083 11.8695 20.13 12.0569 20.1604L13.9828 20.4729C14.3608 20.5342 14.6646 20.1653 14.5319 19.8061L11.9242 12.7464Z"
fill="#6700EB"
/>
<path
d="M9.62773 1.85283L9.92239 1.44888L9.62773 1.85283C9.79939 1.97805 10.0065 2.04534 10.219 2.04493L12.2332 2.0411L12.852 3.95797C12.9173 4.16018 13.0453 4.33634 13.2174 4.4609L14.8493 5.64176L14.2232 7.55627L14.6984 7.71168L14.2232 7.55627C14.1571 7.75822 14.1571 7.97597 14.2232 8.17792L14.8493 10.0924L13.2174 11.2733C13.0453 11.3979 12.9173 11.574 12.852 11.7762L12.2332 13.6931L10.219 13.6893C10.0065 13.6889 9.79939 13.7561 9.62773 13.8814L8.0004 15.0684L6.37307 13.8814C6.20141 13.7561 5.99432 13.6889 5.78184 13.6893L3.76756 13.6931L3.14876 11.7762C3.08349 11.574 2.9555 11.3979 2.78336 11.2733L1.15153 10.0924L1.77762 8.17792C1.84366 7.97597 1.84366 7.75822 1.77762 7.55627L1.15153 5.64176L2.78336 4.4609C2.9555 4.33634 3.08349 4.16018 3.14876 3.95797L3.76756 2.0411L5.78184 2.04493C5.99432 2.04534 6.2014 1.97805 6.37307 1.85283L8.0004 0.665766L9.62773 1.85283Z"
fill="#EEF2F7"
stroke="#6700EB"
/>
<path
d="M9.75129 9.73487C9.87396 9.73487 9.93529 9.78554 9.93529 9.88687V10.8709C9.93529 10.9242 9.91662 10.9669 9.87929 10.9989C9.84729 11.0309 9.79929 11.0469 9.73529 11.0469H6.33529C6.20729 11.0469 6.14329 10.9909 6.14329 10.8789V9.88687C6.14329 9.78554 6.20196 9.73487 6.31929 9.73487H7.03129C7.05796 9.73487 7.07929 9.72954 7.09529 9.71887C7.11662 9.70287 7.12729 9.68421 7.12729 9.66287V6.99087C7.12729 6.94287 7.11662 6.91087 7.09529 6.89487C7.07929 6.87887 7.04462 6.87621 6.99129 6.88687L6.35129 7.03087C6.33529 7.03621 6.31129 7.03887 6.27929 7.03887C6.18329 7.03887 6.13529 6.97487 6.13529 6.84687V6.17487C6.13529 6.11087 6.14329 6.06287 6.15929 6.03087C6.18062 5.99887 6.22062 5.96954 6.27929 5.94287L7.71929 5.22287C7.80996 5.18021 7.88196 5.15087 7.93529 5.13487C7.98862 5.11887 8.04996 5.11087 8.11929 5.11087H8.79929C8.84196 5.11087 8.87662 5.12687 8.90329 5.15887C8.92996 5.18554 8.94329 5.22554 8.94329 5.27887V9.63887C8.94329 9.70287 8.97262 9.73487 9.03129 9.73487H9.75129Z"
fill="#6700EB"
/>
</svg>
)
break
case "gold-sponsor":
reactIcon = (
<svg
className={`icon-large ${iconClassName}`}
width="16"
height="22"
viewBox="0 0 16 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.20835 12.7464C4.30403 12.4873 4.59158 12.3549 4.85062 12.4506L8.5852 13.83C8.84423 13.9257 8.97666 14.2133 8.88098 14.4723L6.30282 21.4521C6.16389 21.8282 5.66094 21.896 5.42738 21.5701L4.56214 20.3627C4.45154 20.2083 4.26306 20.13 4.07563 20.1604L2.14978 20.4729C1.7718 20.5342 1.46799 20.1653 1.60067 19.8061L4.20835 12.7464Z"
fill="#6700EB"
/>
<path
d="M11.9242 12.7464C11.8285 12.4873 11.541 12.3549 11.282 12.4506L7.54737 13.83C7.28834 13.9257 7.15591 14.2133 7.25159 14.4723L9.82975 21.4521C9.96868 21.8282 10.4716 21.896 10.7052 21.5701L11.5704 20.3627C11.681 20.2083 11.8695 20.13 12.0569 20.1604L13.9828 20.4729C14.3608 20.5342 14.6646 20.1653 14.5319 19.8061L11.9242 12.7464Z"
fill="#6700EB"
/>
<path
d="M9.62773 1.85283L9.92239 1.44888L9.62773 1.85283C9.79939 1.97805 10.0065 2.04534 10.219 2.04493L12.2332 2.0411L12.852 3.95797C12.9173 4.16018 13.0453 4.33634 13.2174 4.4609L14.8493 5.64176L14.2232 7.55627L14.6984 7.71168L14.2232 7.55627C14.1571 7.75822 14.1571 7.97597 14.2232 8.17792L14.8493 10.0924L13.2174 11.2733C13.0453 11.3979 12.9173 11.574 12.852 11.7762L12.2332 13.6931L10.219 13.6893C10.0065 13.6889 9.79939 13.7561 9.62773 13.8814L8.0004 15.0684L6.37307 13.8814C6.20141 13.7561 5.99432 13.6889 5.78184 13.6893L3.76756 13.6931L3.14876 11.7762C3.08349 11.574 2.9555 11.3979 2.78336 11.2733L1.15153 10.0924L1.77762 8.17792C1.84366 7.97597 1.84366 7.75822 1.77762 7.55627L1.15153 5.64176L2.78336 4.4609C2.9555 4.33634 3.08349 4.16018 3.14876 3.95797L3.76756 2.0411L5.78184 2.04493C5.99432 2.04534 6.2014 1.97805 6.37307 1.85283L8.0004 0.665766L9.62773 1.85283Z"
fill="#EEF2F7"
stroke="#6700EB"
/>
<path
d="M7.36832 8.42287C7.75232 8.10821 8.02699 7.87087 8.19232 7.71087C8.36299 7.54554 8.47765 7.40954 8.53632 7.30287C8.59499 7.19087 8.62432 7.06821 8.62432 6.93488C8.62432 6.75354 8.56032 6.60687 8.43232 6.49487C8.30965 6.37754 8.14165 6.31887 7.92832 6.31887C7.72565 6.31887 7.54699 6.38554 7.39232 6.51887C7.23765 6.64687 7.07232 6.87354 6.89632 7.19887C6.86965 7.24687 6.82965 7.27087 6.77632 7.27087C6.73899 7.27087 6.69365 7.25754 6.64032 7.23087L5.68832 6.75887C5.61365 6.72154 5.57632 6.66287 5.57632 6.58287C5.57632 6.53487 5.58965 6.48954 5.61632 6.44687C5.91499 5.96154 6.25632 5.60421 6.64032 5.37487C7.02965 5.14554 7.49099 5.03087 8.02432 5.03087C8.50432 5.03087 8.92032 5.11087 9.27232 5.27087C9.62965 5.42554 9.90432 5.64687 10.0963 5.93487C10.2883 6.21754 10.3843 6.54554 10.3843 6.91887C10.3843 7.23354 10.3043 7.51887 10.1443 7.77487C9.98432 8.02554 9.71232 8.27887 9.32832 8.53487L7.92832 9.47087C7.89099 9.49754 7.87232 9.51887 7.87232 9.53487C7.87232 9.55621 7.90699 9.56687 7.97632 9.56687H10.2563C10.379 9.56687 10.4403 9.61754 10.4403 9.71887V10.8709C10.4403 10.9242 10.4217 10.9669 10.3843 10.9989C10.3523 11.0309 10.3043 11.0469 10.2403 11.0469H5.76832C5.68832 11.0469 5.63232 11.0335 5.60032 11.0069C5.56832 10.9802 5.55232 10.9269 5.55232 10.8469V10.0549C5.55232 10.0122 5.57899 9.95621 5.63232 9.88687C5.69099 9.81754 5.82165 9.70021 6.02432 9.53487L7.36832 8.42287Z"
fill="#6700EB"
/>
</svg>
)
break
case "silver-sponsor":
reactIcon = (
<svg
className={`icon-large ${iconClassName}`}
width="16"
height="22"
viewBox="0 0 16 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.20835 12.7464C4.30403 12.4873 4.59158 12.3549 4.85062 12.4506L8.5852 13.83C8.84423 13.9257 8.97666 14.2133 8.88098 14.4723L6.30282 21.4521C6.16389 21.8282 5.66094 21.896 5.42738 21.5701L4.56214 20.3627C4.45154 20.2083 4.26306 20.13 4.07563 20.1604L2.14978 20.4729C1.7718 20.5342 1.46799 20.1653 1.60067 19.8061L4.20835 12.7464Z"
fill="#6700EB"
/>
<path
d="M11.9242 12.7464C11.8285 12.4873 11.541 12.3549 11.282 12.4506L7.54737 13.83C7.28834 13.9257 7.15591 14.2133 7.25159 14.4723L9.82975 21.4521C9.96868 21.8282 10.4716 21.896 10.7052 21.5701L11.5704 20.3627C11.681 20.2083 11.8695 20.13 12.0569 20.1604L13.9828 20.4729C14.3608 20.5342 14.6646 20.1653 14.5319 19.8061L11.9242 12.7464Z"
fill="#6700EB"
/>
<path
d="M9.62773 1.85283L9.92239 1.44888L9.62773 1.85283C9.79939 1.97805 10.0065 2.04534 10.219 2.04493L12.2332 2.0411L12.852 3.95797C12.9173 4.16018 13.0453 4.33634 13.2174 4.4609L14.8493 5.64176L14.2232 7.55627L14.6984 7.71168L14.2232 7.55627C14.1571 7.75822 14.1571 7.97597 14.2232 8.17792L14.8493 10.0924L13.2174 11.2733C13.0453 11.3979 12.9173 11.574 12.852 11.7762L12.2332 13.6931L10.219 13.6893C10.0065 13.6889 9.79939 13.7561 9.62773 13.8814L8.0004 15.0684L6.37307 13.8814C6.20141 13.7561 5.99432 13.6889 5.78184 13.6893L3.76756 13.6931L3.14876 11.7762C3.08349 11.574 2.9555 11.3979 2.78336 11.2733L1.15153 10.0924L1.77762 8.17792C1.84366 7.97597 1.84366 7.75822 1.77762 7.55627L1.15153 5.64176L2.78336 4.4609C2.9555 4.33634 3.08349 4.16018 3.14876 3.95797L3.76756 2.0411L5.78184 2.04493C5.99432 2.04534 6.2014 1.97805 6.37307 1.85283L8.0004 0.665766L9.62773 1.85283Z"
fill="#EEF2F7"
stroke="#6700EB"
/>
<path
d="M5.73732 6.10287C5.71066 6.06554 5.69732 6.03354 5.69732 6.00687C5.69732 5.97487 5.71066 5.93754 5.73732 5.89487C5.92932 5.64421 6.23066 5.43887 6.64132 5.27887C7.05732 5.11354 7.49466 5.03087 7.95332 5.03087C8.44932 5.03087 8.87599 5.09754 9.23332 5.23087C9.59599 5.35887 9.87066 5.54287 10.0573 5.78287C10.2493 6.02287 10.3453 6.30021 10.3453 6.61487C10.3453 6.86021 10.2653 7.09487 10.1053 7.31887C9.95066 7.54287 9.73199 7.69754 9.44932 7.78287C9.38532 7.80421 9.35332 7.83354 9.35332 7.87087C9.35332 7.90821 9.38266 7.93487 9.44132 7.95087C9.77732 8.06821 10.0413 8.24687 10.2333 8.48687C10.4307 8.72154 10.5293 8.98554 10.5293 9.27887C10.5293 10.5109 9.63866 11.1269 7.85732 11.1269C7.36666 11.1269 6.90266 11.0255 6.46532 10.8229C6.02799 10.6202 5.70532 10.3615 5.49732 10.0469C5.47066 10.0042 5.45732 9.96154 5.45732 9.91887C5.45732 9.84421 5.49199 9.78821 5.56132 9.75087L6.36932 9.19087C6.42799 9.15887 6.47332 9.14287 6.50532 9.14287C6.53199 9.14287 6.55866 9.15621 6.58532 9.18287C6.84132 9.44421 7.06266 9.62554 7.24932 9.72687C7.44132 9.82821 7.67599 9.87887 7.95332 9.87887C8.19866 9.87887 8.39066 9.82821 8.52932 9.72687C8.67332 9.62554 8.74532 9.48687 8.74532 9.31087C8.74532 9.12421 8.66799 8.96954 8.51332 8.84687C8.35866 8.72421 8.15332 8.66021 7.89732 8.65487L7.22532 8.63887C7.11332 8.63887 7.05732 8.59354 7.05732 8.50287V7.53487C7.05732 7.46021 7.11332 7.41487 7.22532 7.39887L7.76132 7.36687C8.01199 7.35087 8.20932 7.28687 8.35332 7.17487C8.50266 7.05754 8.57732 6.91087 8.57732 6.73487C8.57732 6.59621 8.51332 6.48154 8.38532 6.39087C8.25732 6.29487 8.10266 6.24687 7.92132 6.24687C7.69199 6.24687 7.48666 6.28954 7.30532 6.37487C7.12399 6.46021 6.92399 6.60687 6.70532 6.81487C6.66266 6.85754 6.63066 6.87887 6.60932 6.87887C6.59332 6.87887 6.56132 6.86021 6.51332 6.82288L5.73732 6.10287Z"
fill="#6700EB"
/>
</svg>
)
break
case "bronze-sponsor":
reactIcon = (
<svg
className={`icon-large ${iconClassName}`}
width="16"
height="22"
viewBox="0 0 16 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.20835 12.7464C4.30403 12.4873 4.59158 12.3549 4.85062 12.4506L8.5852 13.83C8.84423 13.9257 8.97666 14.2133 8.88098 14.4723L6.30282 21.4521C6.16389 21.8282 5.66094 21.896 5.42738 21.5701L4.56214 20.3627C4.45154 20.2083 4.26306 20.13 4.07563 20.1604L2.14978 20.4729C1.7718 20.5342 1.46799 20.1653 1.60067 19.8061L4.20835 12.7464Z"
fill="#6700EB"
/>
<path
d="M11.9242 12.7464C11.8285 12.4873 11.541 12.3549 11.282 12.4506L7.54737 13.83C7.28834 13.9257 7.15591 14.2133 7.25159 14.4723L9.82975 21.4521C9.96868 21.8282 10.4716 21.896 10.7052 21.5701L11.5704 20.3627C11.681 20.2083 11.8695 20.13 12.0569 20.1604L13.9828 20.4729C14.3608 20.5342 14.6646 20.1653 14.5319 19.8061L11.9242 12.7464Z"
fill="#6700EB"
/>
<path
d="M9.62773 1.85283L9.92239 1.44888L9.62773 1.85283C9.79939 1.97805 10.0065 2.04534 10.219 2.04493L12.2332 2.0411L12.852 3.95797C12.9173 4.16018 13.0453 4.33634 13.2174 4.4609L14.8493 5.64176L14.2232 7.55627L14.6984 7.71168L14.2232 7.55627C14.1571 7.75822 14.1571 7.97597 14.2232 8.17792L14.8493 10.0924L13.2174 11.2733C13.0453 11.3979 12.9173 11.574 12.852 11.7762L12.2332 13.6931L10.219 13.6893C10.0065 13.6889 9.79939 13.7561 9.62773 13.8814L8.0004 15.0684L6.37307 13.8814C6.20141 13.7561 5.99432 13.6889 5.78184 13.6893L3.76756 13.6931L3.14876 11.7762C3.08349 11.574 2.9555 11.3979 2.78336 11.2733L1.15153 10.0924L1.77762 8.17792C1.84366 7.97597 1.84366 7.75822 1.77762 7.55627L1.15153 5.64176L2.78336 4.4609C2.9555 4.33634 3.08349 4.16018 3.14876 3.95797L3.76756 2.0411L5.78184 2.04493C5.99432 2.04534 6.2014 1.97805 6.37307 1.85283L8.0004 0.665766L9.62773 1.85283Z"
fill="#EEF2F7"
stroke="#6700EB"
/>
<path
d="M7.93232 5.31087C8.00166 5.22554 8.06032 5.17221 8.10832 5.15087C8.15632 5.12421 8.23366 5.11087 8.34032 5.11087H9.65232C9.70032 5.11087 9.73766 5.12687 9.76432 5.15887C9.79099 5.18554 9.80432 5.22554 9.80432 5.27887V8.47087C9.80432 8.53487 9.83366 8.56687 9.89232 8.56687H10.4843C10.6017 8.56687 10.6603 8.61754 10.6603 8.71887V9.59887C10.6603 9.71621 10.5963 9.77487 10.4683 9.77487H9.90032C9.83632 9.77487 9.80432 9.80954 9.80432 9.87887V10.8149C9.80432 10.9002 9.78299 10.9615 9.74032 10.9989C9.69766 11.0309 9.63099 11.0469 9.54032 11.0469H8.17232C8.09232 11.0469 8.03632 11.0309 8.00432 10.9989C7.97232 10.9669 7.95632 10.9162 7.95632 10.8469L7.96432 9.85487C7.96432 9.80154 7.92699 9.77487 7.85232 9.77487H5.57232C5.50832 9.77487 5.45766 9.75887 5.42032 9.72687C5.38832 9.69487 5.37232 9.65221 5.37232 9.59887V8.76687C5.37232 8.70821 5.39099 8.64687 5.42832 8.58287C5.46566 8.51354 5.53499 8.41487 5.63632 8.28687L7.93232 5.31087ZM7.91632 8.56687C7.93766 8.56687 7.95366 8.56154 7.96432 8.55087C7.98032 8.54021 7.98832 8.52687 7.98832 8.51087V6.96687C7.98832 6.88687 7.98032 6.84687 7.96432 6.84687C7.94832 6.84687 7.91899 6.87621 7.87632 6.93488L6.78032 8.46287C6.75899 8.49487 6.74832 8.51621 6.74832 8.52687C6.74832 8.55354 6.77766 8.56687 6.83632 8.56687H7.91632Z"
fill="#6700EB"
/>
</svg>
)
break
case "seedling-sponsor":
reactIcon = (
<svg
className={`icon-large ${iconClassName}`}
width="16"
height="22"
viewBox="0 0 16 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.20833 12.7464C4.30401 12.4873 4.59157 12.3549 4.8506 12.4506L8.58518 13.83C8.84422 13.9257 8.97664 14.2133 8.88096 14.4723L6.30281 21.4521C6.16387 21.8282 5.66092 21.896 5.42736 21.5701L4.56212 20.3627C4.45152 20.2083 4.26304 20.13 4.07562 20.1604L2.14976 20.4729C1.77179 20.5342 1.46798 20.1653 1.60066 19.8061L4.20833 12.7464Z"
fill="#6700EB"
/>
<path
d="M11.9243 12.7464C11.8286 12.4873 11.541 12.3549 11.282 12.4506L7.54743 13.83C7.2884 13.9257 7.15597 14.2133 7.25165 14.4723L9.82981 21.4521C9.96874 21.8282 10.4717 21.896 10.7053 21.5701L11.5705 20.3627C11.6811 20.2083 11.8696 20.13 12.057 20.1604L13.9828 20.4729C14.3608 20.5342 14.6646 20.1653 14.532 19.8061L11.9243 12.7464Z"
fill="#6700EB"
/>
<path
d="M8.0004 0.665766L9.62773 1.85283C9.79939 1.97805 10.0065 2.04534 10.219 2.04493L12.2332 2.0411L12.852 3.95797C12.9173 4.16018 13.0453 4.33634 13.2174 4.4609L14.8493 5.64176L14.2232 7.55627C14.1571 7.75822 14.1571 7.97597 14.2232 8.17792L14.8493 10.0924L13.2174 11.2733C13.0453 11.3979 12.9173 11.574 12.852 11.7762L12.2332 13.6931L10.219 13.6893C10.0065 13.6889 9.79939 13.7561 9.62773 13.8814L8.0004 15.0684L6.37307 13.8814C6.2014 13.7561 5.99432 13.6889 5.78184 13.6893L3.76756 13.6931L3.14876 11.7762C3.08349 11.574 2.9555 11.3979 2.78336 11.2733L1.15153 10.0924L1.77762 8.17792C1.84366 7.97597 1.84366 7.75822 1.77762 7.55627L1.15153 5.64176L2.78336 4.4609C2.9555 4.33634 3.08349 4.16018 3.14876 3.95797L3.76756 2.0411L5.78184 2.04493C5.99432 2.04534 6.2014 1.97805 6.37307 1.85283L8.0004 0.665766Z"
fill="#EEF2F7"
stroke="#6700EB"
/>
<path
d="M9.83389 5.11087C9.94589 5.11087 10.0152 5.12421 10.0419 5.15087C10.0686 5.17754 10.0792 5.24154 10.0739 5.34287L10.0259 6.36687C10.0206 6.43621 9.99922 6.48421 9.96189 6.51087C9.92989 6.53754 9.87655 6.55087 9.80189 6.55087H7.40989C7.30322 6.55087 7.24722 6.59887 7.24189 6.69487L7.17789 7.23087V7.26287C7.17789 7.30021 7.18855 7.32687 7.20989 7.34287C7.23655 7.35354 7.26855 7.35354 7.30589 7.34287C7.54589 7.27887 7.73522 7.23621 7.87389 7.21487C8.01255 7.18821 8.16455 7.17487 8.32989 7.17487C8.76189 7.17487 9.14322 7.25221 9.47389 7.40687C9.80989 7.56154 10.0712 7.78554 10.2579 8.07887C10.4499 8.36687 10.5459 8.71087 10.5459 9.11087C10.5459 9.52687 10.4419 9.88687 10.2339 10.1909C10.0312 10.4949 9.74055 10.7269 9.36189 10.8869C8.98322 11.0469 8.54322 11.1269 8.04189 11.1269C7.47122 11.1269 6.95922 11.0309 6.50589 10.8389C6.05255 10.6415 5.72189 10.3882 5.51389 10.0789C5.46055 9.99354 5.43389 9.92954 5.43389 9.88687C5.43389 9.82821 5.46589 9.78021 5.52989 9.74287L6.39389 9.15887C6.42589 9.13221 6.46589 9.11887 6.51389 9.11887C6.57255 9.11887 6.62055 9.14287 6.65789 9.19087C6.85522 9.42554 7.03655 9.59887 7.20189 9.71087C7.37255 9.82287 7.58855 9.87887 7.84989 9.87887C8.13789 9.87887 8.36189 9.81221 8.52189 9.67887C8.68189 9.54021 8.76189 9.36954 8.76189 9.16687C8.76189 8.96421 8.68455 8.79354 8.52989 8.65487C8.38055 8.51087 8.16189 8.43887 7.87389 8.43887C7.75122 8.43887 7.63122 8.45487 7.51389 8.48687C7.40189 8.51887 7.25522 8.57487 7.07389 8.65487C7.03122 8.67621 6.99389 8.68687 6.96189 8.68687C6.94055 8.68687 6.89789 8.67087 6.83389 8.63887L5.88189 8.12687C5.83922 8.10554 5.80989 8.08154 5.79389 8.05487C5.77789 8.02287 5.76989 7.98287 5.76989 7.93487C5.76989 7.88687 5.77255 7.84954 5.77789 7.82287L6.00989 5.35087C6.02055 5.25487 6.04722 5.19087 6.08989 5.15887C6.13789 5.12687 6.23122 5.11087 6.36989 5.11087H9.83389Z"
fill="#6700EB"
/>
</svg>
)
break
default:
throw new Error("Invalid icon " + name)
}
let backgroundClassName = "col-start-1 row-start-1 icon-background"
switch (variant) {
case "dark":
backgroundClassName += " text-white"
break
case "light":
backgroundClassName += " text-off-white"
break
case "light-hover":
backgroundClassName += " text-off-white hover:text-blue-light"
break
case "custom":
backgroundClassName += " " + customBackgroundClassName
break
default:
throw new Error("Invalid variant " + variant)
}
return (
<span
className={`inline-grid items-center grid-cols-1 grid-rows-1 justify-items-center ${className}`}
>
<FaCircle className={backgroundClassName}></FaCircle>
{reactIcon}
</span>
)
}
export {Icon}

View File

@@ -0,0 +1,10 @@
const LinkList = ({title, className = "", children}) => {
return (
<div className={`text-sm space-y-2 flex flex-col font-secondary ${className}`}>
<h3 className="font-semibold">{title}</h3>
{children}
</div>
)
}
export {LinkList}

View File

@@ -0,0 +1,30 @@
import React from "react"
import {BsArrowRight} from "react-icons/bs"
// Video player component we can pass a url to using the react-player library.
// control prop determinse whether video player controls will be displayed.
const NewsletterForm = ({className, hasDarkMode}) => {
return (
<form
action="https://design.us4.list-manage.com/subscribe/post?u=aeb422edfecb0e2dcaf70d12d&amp;id=1a028d02ce"
method="post"
className={`relative ${className} border ${
hasDarkMode ? "border-blue-light dark:border-white" : "border-white"
} border-opacity-50 rounder-sm font-secondary`}
>
<input
aria-label="Email Address"
name="EMAIL"
type="email"
required
placeholder="Enter Your Email Address"
className={`w-[90%] p-2 text-base placeholder-current bg-transparent outline-none`}
/>
<button className="absolute right-0 mt-2 mr-2" type="submit">
<BsArrowRight size="1.5rem" className="justify-self-end" />
</button>
</form>
)
}
export {NewsletterForm}

View File

@@ -0,0 +1,16 @@
import {Icon} from "@/components/home/Icon"
const Sponsor = ({title, iconName, children}) => {
return (
<div className="text-sm font-secondary">
<Icon name={iconName} />
<br className="hidden lg:block" />
<h3 className="inline-block mb-3 ml-2 text-lg font-semibold align-top font-primary lg:ml-0 lg:mt-2 xl:text-xl">
{title}
</h3>
<div className="flex space-x-2">{children}</div>
</div>
)
}
export {Sponsor}

View File

@@ -0,0 +1,13 @@
import React from "react"
const StyledLink = React.forwardRef(({href, children, className, ...props}, ref) => {
return (
<a href={href} {...props} className={`hover:text-blue-mid ${className}`} ref={ref}>
{children}
</a>
)
})
StyledLink.displayName = "StyledLink"
export {StyledLink}

View File

@@ -0,0 +1,30 @@
import {useState, useEffect} from "react"
import dynamic from "next/dynamic"
const ReactPlayer = dynamic(() => import("react-player/lazy"), {
suspense: false,
})
const VideoPlayer = ({url, className = ""}) => {
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
return (
<div className="player-wrapper">
{mounted ? (
<ReactPlayer
className={`react-player ${className}`}
url={url}
width="100%"
height="100%"
controls={true}
light={true}
/>
) : null}
</div>
)
}
export {VideoPlayer}

View File

@@ -0,0 +1,53 @@
import {default as BlitzLink} from "next/link"
export function IconContainer({as: Component = "div", color, className = "", ...props}) {
return (
<Component
className={`w-12 h-12 rounded-xl mb-8 bg-gradient-to-br flex items-center justify-center ${className}`}
{...props}
/>
)
}
export function Caption({as: Component = "p", className = "", ...props}) {
return (
<Component
className={`sm:text-lg sm:leading-snug font-semibold tracking-wide uppercase ${className}`}
{...props}
/>
)
}
export function BigText({as: Component = "p", className = "", ...props}) {
return (
<Component
className={`text-3xl sm:text-5xl lg:text-6xl leading-none font-extrabold text-gray-900 tracking-tight ${className}`}
{...props}
/>
)
}
export function Paragraph({as: Component = "p", className = "", ...props}) {
return (
<Component
className={`max-w-4xl text-lg sm:text-2xl font-medium sm:leading-10 space-y-6 ${className}`}
{...props}
/>
)
}
export function Link({className = "", href, ...props}) {
return (
<BlitzLink href={href}>
{/* eslint-disable-next-line */}
<a
className={`inline-flex text-lg sm:text-2xl font-medium transition-colors duration-200 focus:ring-2 focus:ring-offset-2 focus:ring-current focus:outline-none rounded-md ${className}`}
{...props}
/>
</BlitzLink>
)
}
export function InlineCode({className = "", ...props}) {
return <code className={`font-mono text-gray-900 font-bold ${className}`} {...props} />
}

View File

@@ -0,0 +1,6 @@
import {useMedia} from "@/hooks/useMedia"
export const useIsDesktop = () => {
const matches = useMedia("(min-width: 1024px)")
return matches
}

View File

@@ -0,0 +1,5 @@
import {useRouter} from "next/router"
export function useIsDocsIndex() {
return useRouter().pathname === "/docs"
}

View File

@@ -0,0 +1,5 @@
import {useRouter} from "next/router"
export function useIsHome() {
return useRouter().pathname === "/"
}

View File

@@ -0,0 +1,3 @@
import {useEffect, useLayoutEffect} from "react"
export const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect

View File

@@ -0,0 +1,27 @@
// https://github.com/streamich/react-use/blob/master/src/useMedia.ts
import {useEffect, useState} from "react"
export function useMedia(query, defaultState = false) {
const [state, setState] = useState(defaultState)
useEffect(() => {
let mounted = true
const mql = window.matchMedia(query)
const onChange = () => {
if (!mounted) {
return
}
setState(!!mql.matches)
}
mql.addListener(onChange)
setState(mql.matches)
return () => {
mounted = false
mql.removeListener(onChange)
}
}, [query])
return state
}

View File

@@ -0,0 +1,15 @@
import {useRouter} from "next/router"
import {useContext} from "react"
import {SidebarContext} from "@/layouts/SidebarLayout"
export function usePrevNext() {
let router = useRouter()
let {nav} = useContext(SidebarContext)
let pages = nav.flatMap((category) => category.pages)
let pageIndex = pages.findIndex((page) => page.href === router.pathname)
return {
prev: pageIndex > -1 ? pages[pageIndex - 1] : undefined,
next: pageIndex > -1 ? pages[pageIndex + 1] : undefined,
}
}

View File

@@ -0,0 +1,16 @@
// https://usehooks.com/usePrevious/
import {useEffect, useRef} from "react"
export function usePrevious(value) {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref = useRef()
// Store current value in ref
useEffect(() => {
ref.current = value
}, [value]) // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current
}

View File

@@ -0,0 +1,16 @@
import {useRect} from "@reach/rect"
import {useEffect, useState} from "react"
export function useTop(ref) {
let [top, setTop] = useState()
let rect = useRect(ref)
let rectTop = rect ? rect.top : undefined
useEffect(() => {
if (typeof rectTop === "undefined") return
let newTop = rectTop + window.pageYOffset
if (newTop !== top) {
setTop(newTop)
}
}, [rectTop, top])
return top
}

View File

@@ -0,0 +1,220 @@
import Link from "next/link"
import {useRouter} from "next/router"
import clsx from "clsx"
import {createContext, Fragment, useCallback, useContext, useEffect, useState} from "react"
import {BiChevronLeft} from "react-icons/bi"
import {BsArrowLeft, BsArrowRight, BsCaretDownFill, BsCaretUpFill} from "react-icons/bs"
import {FaGithub} from "react-icons/fa"
import Select, {components} from "react-select"
import {PageHeader} from "../components/PageHeader"
import {usePrevNext} from "../hooks/usePrevNext"
export const ContentsContext = createContext()
export function TableOfContents({tableOfContents, currentSection}) {
return (
<div className="pl-8">
<ul className="overflow-x-hidden text-black dark:text-dark-mode-text font-normal text-xs">
{tableOfContents.map((section) => {
let sectionIsActive =
currentSection === section.slug ||
section.children.findIndex(({slug}) => slug === currentSection) > -1
return (
<Fragment key={section.slug}>
<li>
<a
href={`#${section.slug}`}
className={clsx(
"block transform transition-colors duration-200 py-2 hover:text-gray-600 dark:hover:text-gray-300 no-underline",
{
"font-bold": sectionIsActive,
},
)}
>
{section.title}
</a>
</li>
{section.children.map((subsection) => {
let subsectionIsActive = currentSection === subsection.slug
return (
<li className="ml-2" key={subsection.slug}>
<a
href={`#${subsection.slug}`}
className={clsx(
"block py-2 transition-colors duration-200 hover:text-gray-700 dark:hover:text-gray-300 no-underline",
{
"font-bold": subsectionIsActive,
},
)}
>
{subsection.title}
</a>
</li>
)
})}
</Fragment>
)
})}
</ul>
</div>
)
}
function useTableOfContents(tableOfContents) {
let [currentSection, setCurrentSection] = useState(tableOfContents[0]?.slug)
let [headings, setHeadings] = useState([])
const registerHeading = useCallback((id, top) => {
setHeadings((headings) => [...headings.filter((h) => id !== h.id), {id, top}])
}, [])
const unregisterHeading = useCallback((id) => {
setHeadings((headings) => headings.filter((h) => id !== h.id))
}, [])
useEffect(() => {
if (tableOfContents.length === 0 || headings.length === 0) return
function onScroll() {
let y = window.pageYOffset
let windowHeight = window.innerHeight
let sortedHeadings = headings.concat([]).sort((a, b) => a.top - b.top)
if (y <= 0) {
setCurrentSection(sortedHeadings[0].id)
return
}
if (y + windowHeight >= document.body.scrollHeight) {
setCurrentSection(sortedHeadings[sortedHeadings.length - 1].id)
return
}
const middle = y + windowHeight / 2
let current = sortedHeadings[0].id
for (let i = 0; i < sortedHeadings.length; i++) {
if (middle >= sortedHeadings[i].top) {
current = sortedHeadings[i].id
}
}
setCurrentSection(current)
}
window.addEventListener("scroll", onScroll, {
capture: true,
passive: true,
})
onScroll()
return () => window.removeEventListener("scroll", onScroll, true)
}, [headings, tableOfContents])
return {currentSection, registerHeading, unregisterHeading}
}
const DropdownIndicator = (props) => {
return (
components.DropdownIndicator && (
<components.DropdownIndicator {...props}>
<BsCaretUpFill size="10" className="text-black dark:text-dark-mode-text" />
<BsCaretDownFill
size="10"
className="text-black dark:text-dark-mode-text"
style={{marginTop: -2}}
/>
</components.DropdownIndicator>
)
)
}
export function ContentsLayout({children, meta, tableOfContents: toc}) {
const {registerHeading, unregisterHeading} = useTableOfContents(toc)
let {prev, next} = usePrevNext()
const router = useRouter()
const [topic, setTopic] = useState(null)
return (
<>
<Link href="/docs">
<a className="lg:hidden mx-6 text-xxs px-2.5 py-0.5 rounded-sm bg-off-white font-primary inline-flex mb-4 dark:bg-purple-off-black -mt-4 items-center">
<BiChevronLeft size={18} /> Back to Documentation Menu
</a>
</Link>
<div id={meta.containerId} className="pt-4 pb-8 w-full flex">
<div className="min-w-0 flex-auto px-6 sm:px-8 xl:px-12">
<PageHeader title={meta.title} align={meta.titleAlign ?? "left"} />
<div className={clsx("lg:hidden", {"mt-8 mb-12": toc.length, "h-px mt-12": !toc.length})}>
{!!toc.length && (
<>
<h3 className="dark:text-dark-mode-text mb-2 text-sm">Topics</h3>
<Select
value={topic}
className="topic-select"
classNamePrefix="topic-select"
options={toc.map((option) => ({value: option.slug, label: option.title}))}
placeholder="Jump to a Topic"
onChange={(e) => {
if (e && e.value) {
const hash = e.value
setTopic(null)
router.push({hash})
}
}}
components={{DropdownIndicator}}
styles={{
option: (base, state) => ({
...base,
backgroundColor: state.isSelected ? null : null,
}),
}}
/>
</>
)}
</div>
<ContentsContext.Provider value={{registerHeading, unregisterHeading}}>
{children}
</ContentsContext.Provider>
{!meta.hideFooter && (
<>
<hr className="border-gray-200 mt-10 mb-4" />
<a
href={
"https://github.com/blitz-js/blitzjs.com/edit/main/app/pages" +
router.asPath.split("#")[0] +
".mdx"
}
target="_blank"
rel="noopener noreferrer"
className="flex items-center py-2 text-sm"
>
<FaGithub className="mr-3" /> Idea for improving this page? Edit it on GitHub.
</a>
{(prev || next) && (
<>
<div className="flex flex-col sm:flex-row justify-between leading-7 text-lg font-semibold mt-8 mb-6">
{prev && (
<Link href={prev.href}>
<a className="flex items-center">
<BsArrowLeft className="icon-large mr-2 fill-current" />{" "}
{prev.sidebar_label || prev.title}
</a>
</Link>
)}
<div className="spacer px-3"></div>
{next && (
<Link href={next.href}>
<a className="flex justify-end">
<div className="flex items-center">
{next.sidebar_label || next.title}{" "}
<BsArrowRight className="icon-large ml-2 fill-current transform rotate-180" />
</div>
</a>
</Link>
)}
</div>
</>
)}
</>
)}
</div>
</div>
</>
)
}

View File

@@ -0,0 +1,41 @@
import {useEffect, useState} from "react"
import {Header} from "@/components/Header"
import {Footer} from "@/components/home/Footer"
import {SocialCards} from "@/components/SocialCards"
import {Title} from "@/components/Title"
import {ContentsLayout} from "@/layouts/ContentsLayout"
import {SidebarLayout} from "@/layouts/SidebarLayout"
import documentationNav from "@/navs/documentation.json"
export function DocumentationLayout({children, ...props}) {
const [navIsOpen, setNavIsOpen] = useState(false)
useEffect(() => {
document.body.style.overflow = navIsOpen ? "hidden" : "unset"
}, [navIsOpen])
return (
<div className="bg-white dark:bg-purple-deep pb-1 md:pb-3">
<Title>{props?.meta?.title}</Title>
<SocialCards imageUrl="/social-docs.png" />
<Header
className="px-6 mx-auto max-w-7xl"
hasLightBg
useColoredLogo
stickyBgClass="bg-white dark:bg-purple-deep"
hasFade
onNavToggle={(isOpen) => {
setNavIsOpen(isOpen)
}}
/>
<div className="max-w-7xl mx-auto font-secondary dark:">
<SidebarLayout nav={documentationNav} {...props}>
<ContentsLayout {...props}>{children}</ContentsLayout>
</SidebarLayout>
</div>
<Footer className="text-black dark:text-dark-mode-text" hasDarkMode />
</div>
)
}

View File

@@ -0,0 +1,150 @@
import Link from "next/link"
import {useRouter} from "next/router"
import clsx from "clsx"
import {createContext, forwardRef, Fragment, useEffect, useRef} from "react"
import {PageHeader} from "@/components/PageHeader"
import {SidebarTitle} from "@/components/SidebarTitle"
import {useIsDesktop} from "@/hooks/useIsDesktop"
import {useIsDocsIndex} from "@/hooks/useIsDocsIndex"
import {useIsomorphicLayoutEffect} from "@/hooks/useIsomorphicLayoutEffect"
import {TableOfContents} from "@/layouts/ContentsLayout"
export const SidebarContext = createContext()
const NavItem = forwardRef(({href, children, isActive, isPublished, fallbackHref}, ref) => {
return (
<li ref={ref}>
<Link href={isPublished ? href : fallbackHref}>
<a
className={clsx("px-3 py-2 transition-colors duration-200 relative block font-semibold", {
underline: isActive,
"hover:underline": !isActive && isPublished,
"text-gray-400": !isActive && !isPublished,
})}
style={{letterSpacing: 0.3}}
>
<span className="relative">{children}</span>
</a>
</Link>
</li>
)
})
NavItem.displayName = "NavItem"
function Nav({nav, children, fallbackHref, toc}) {
const router = useRouter()
const activeItemRef = useRef()
const scrollRef = useRef()
useIsomorphicLayoutEffect(() => {
if (activeItemRef.current) {
const scrollRect = scrollRef.current.getBoundingClientRect()
const activeItemRect = activeItemRef.current.getBoundingClientRect()
scrollRef.current.scrollTop =
activeItemRect.top - scrollRect.top - scrollRect.height / 2 + activeItemRect.height / 2
}
}, [])
return (
// eslint-disable-next-line
<div
id="navWrapper"
onClick={(e) => e.stopPropagation()}
className="h-full scrolling-touch lg:h-auto lg:block lg:sticky lg:bg-transparent overflow-hidden lg:top-18 bg-white dark:bg-purple-deep mr-24 lg:mr-0"
>
<div className="absolute bg-gradient-to-b from-white dark:from-purple-deep h-12 lg:block pointer-events-none w-full z-10"></div>
<nav
id="nav"
ref={scrollRef}
className="px-1 bg-white dark:bg-purple-deep font-medium text-base sm:px-3 xl:px-5 pb-10 lg:pb-16 sticky?lg:h-(screen-18) lg:overflow-y-auto"
>
<ul className="space-y-16 mt-10">
{children}
{nav &&
nav
.map((category) => {
let publishedItems = category.pages.filter((item) => item.published !== false)
if (publishedItems.length === 0 && !fallbackHref) return null
return (
<li key={category.title.title} className="">
<SidebarTitle {...category.title} />
<ul>
{(fallbackHref ? category.pages : publishedItems).map((item, i) => (
<Fragment key={i}>
<NavItem
href={item.displayUrl ?? item.href}
isActive={item.href === router.pathname}
ref={item.href === router.pathname ? activeItemRef : undefined}
isPublished={item.published !== false}
fallbackHref={fallbackHref}
>
{item.sidebar_label || item.title}
</NavItem>
{item.href === router.pathname && toc && toc.length ? (
<TableOfContents tableOfContents={toc} />
) : null}
</Fragment>
))}
</ul>
</li>
)
})
.filter(Boolean)}
</ul>
</nav>
</div>
)
}
export function SidebarLayout({children, nav, sidebar, fallbackHref, tableOfContents}) {
const router = useRouter()
const isDocsIndex = useIsDocsIndex()
const isDesktop = useIsDesktop()
useEffect(() => {
if (isDesktop && isDocsIndex) {
router.push("/docs/get-started")
}
}, [router, isDesktop, isDocsIndex])
return (
<SidebarContext.Provider value={{nav}}>
<div
className={clsx("w-full max-w-8xl mx-auto lg:mt-16", {
"mt-10": true,
})}
>
<div className="lg:flex">
{/* eslint-disable-next-line */}
<div
id="sidebar"
className="hidden fixed z-40 inset-0 flex-none h-full bg-opacity-25 w-full lg:static lg:h-auto lg:overflow-y-visible lg:pt-0 lg:w-80 xl:w-96 lg:block"
>
<Nav nav={nav} fallbackHref={fallbackHref} toc={tableOfContents}>
{sidebar}
</Nav>
</div>
<div
id="content-wrapper"
className="min-w-0 w-full flex-auto lg:static lg:overflow-visible max-w-3xl"
>
{isDocsIndex && !isDesktop ? (
<div className="">
<div className="px-4 lg:px-8">
<PageHeader title="Docs" />
</div>
<Nav nav={nav} fallbackHref={fallbackHref} toc={tableOfContents}>
{sidebar}
</Nav>
</div>
) : (
children
)}
</div>
</div>
</div>
</SidebarContext.Provider>
)
}

View File

@@ -0,0 +1,163 @@
const {createMacro} = require("babel-plugin-macros")
const Prism = require("prismjs")
require("prismjs/components/prism-jsx")
const {parseExpression} = require("@babel/parser")
const generate = require("@babel/generator").default
function simplify(token) {
if (typeof token === "string") return token
return [token.type, Array.isArray(token.content) ? token.content.map(simplify) : token.content]
}
function tokenizeMacro({references, babel: {types: t}}) {
if (references.default) {
references.default.forEach(createTransform("tokens"))
}
if (references.tokenizeWithLines) {
references.tokenizeWithLines.forEach(createTransform("lines"))
}
function createTransform(type) {
return (path) => {
const lang = path.parentPath.node.property.name
const codeNode = path.parentPath.parentPath.node.arguments[0]
const originalCode = t.isTemplateLiteral(codeNode)
? codeNode.quasis[0].value.cooked
: codeNode.value
const returnCodeNode = path.parentPath.parentPath.node.arguments[1]
const returnCode = returnCodeNode && returnCodeNode.value
const argsNode = path.parentPath.parentPath.node.arguments[3]
let args = {}
if (argsNode) {
eval("args = " + generate(argsNode).code)
}
const codeTransformerNode = path.parentPath.parentPath.node.arguments[2]
let code = originalCode
if (codeTransformerNode) {
const codeTransformer = eval(generate(codeTransformerNode).code)
code = codeTransformer(code, args)
}
const tokens = Prism.tokenize(code, Prism.languages[lang])
path.parentPath.parentPath.replaceWith(
parseExpression(
JSON.stringify({
...(type === "tokens" ? {tokens: tokens.map(simplify)} : {}),
...(type === "lines" ? {lines: normalizeTokens(tokens)} : {}),
...(returnCode ? {code: returnCode === "original" ? originalCode : code} : {}),
...args,
}),
),
)
}
}
}
// https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/utils/normalizeTokens.js
const newlineRe = /\r\n|\r|\n/
// Empty lines need to contain a single empty token, denoted with { empty: true }
function normalizeEmptyLines(line) {
if (line.length === 0) {
line.push({
types: ["plain"],
content: "",
empty: true,
})
} else if (line.length === 1 && line[0].content === "") {
line[0].empty = true
}
}
function appendTypes(types, add) {
const typesSize = types.length
if (typesSize > 0 && types[typesSize - 1] === add) {
return types
}
return types.concat(add)
}
// Takes an array of Prism's tokens and groups them by line, turning plain
// strings into tokens as well. Tokens can become recursive in some cases,
// which means that their types are concatenated. Plain-string tokens however
// are always of type "plain".
// This is not recursive to avoid exceeding the call-stack limit, since it's unclear
// how nested Prism's tokens can become
function normalizeTokens(tokens) {
const typeArrStack = [[]]
const tokenArrStack = [tokens]
const tokenArrIndexStack = [0]
const tokenArrSizeStack = [tokens.length]
let i = 0
let stackIndex = 0
let currentLine = []
const acc = [currentLine]
while (stackIndex > -1) {
while ((i = tokenArrIndexStack[stackIndex]++) < tokenArrSizeStack[stackIndex]) {
let content
let types = typeArrStack[stackIndex]
const tokenArr = tokenArrStack[stackIndex]
const token = tokenArr[i]
// Determine content and append type to types if necessary
if (typeof token === "string") {
types = stackIndex > 0 ? types : ["plain"]
content = token
} else {
types = appendTypes(types, token.type)
if (token.alias) {
types = appendTypes(types, token.alias)
}
content = token.content
}
// If token.content is an array, increase the stack depth and repeat this while-loop
if (typeof content !== "string") {
stackIndex++
typeArrStack.push(types)
tokenArrStack.push(content)
tokenArrIndexStack.push(0)
tokenArrSizeStack.push(content.length)
continue
}
// Split by newlines
const splitByNewlines = content.split(newlineRe)
const newlineCount = splitByNewlines.length
currentLine.push({types, content: splitByNewlines[0]})
// Create a new line for each string on a new line
for (let i = 1; i < newlineCount; i++) {
normalizeEmptyLines(currentLine)
acc.push((currentLine = []))
currentLine.push({types, content: splitByNewlines[i]})
}
}
// Decreate the stack depth
stackIndex--
typeArrStack.pop()
tokenArrStack.pop()
tokenArrIndexStack.pop()
tokenArrSizeStack.pop()
}
normalizeEmptyLines(currentLine)
return acc
}
module.exports = createMacro(tokenizeMacro)

View File

@@ -0,0 +1,140 @@
[
{
"title": {
"title": "Introduction",
"iconPath": "/img/introduction.svg",
"iconDarkPath": "/img/introduction-white.svg"
},
"pages": [
"why-blitz",
"get-started",
"learning-path",
"tutorial",
"blitz-with-next",
"upgrading-from-framework",
"stickers"
]
},
{
"title": {
"title": "Community",
"iconPath": "/img/people-purple.svg",
"iconDarkPath": "/img/people-white.svg"
},
"pages": [
"how-the-community-operates",
"manifesto",
"community-history",
"contributing",
"maintainers",
"code-of-conduct",
"translations"
]
},
{
"title": {
"title": "Basics",
"iconPath": "/img/basics.svg",
"iconDarkPath": "/img/basics-white.svg"
},
"pages": [
"file-structure",
"custom-environments",
"error-handling",
"testing",
"middleware",
"utilities",
"client-and-server",
"troubleshooting"
]
},
{
"title": {
"title": "Framework Adapters",
"iconPath": "/img/pages.svg",
"iconDarkPath": "/img/pages-white.svg"
},
"pages": ["blitzjs-next"]
},
{
"title": {
"title": "@blitzjs/auth",
"iconPath": "/img/shield-purple.svg",
"iconDarkPath": "/img/shield-white.svg"
},
"pages": [
"auth",
"auth-setup",
"session-management",
"authorization",
"passportjs",
"auth-utils",
"impersonation"
]
},
{
"title": {
"title": "@blitzjs/rpc",
"iconPath": "/img/queries.svg",
"iconDarkPath": "/img/queries-white.svg"
},
"pages": [
"rpc-overview",
"rpc-setup",
"query-resolvers",
"query-usage",
"mutation-resolvers",
"mutation-usage",
"resolver-client-utilities",
"resolver-server-utilities",
"use-query",
"use-paginated-query",
"use-infinite-query",
"use-mutation",
"rpc-specification"
]
},
{
"title": {
"title": "Backend Architecture",
"iconPath": "/img/mutations.svg",
"iconDarkPath": "/img/mutations-white.svg"
},
"pages": ["multitenancy"]
},
{
"title": {
"title": "Database",
"iconPath": "/img/database.svg",
"iconDarkPath": "/img/database-white.svg"
},
"pages": ["database-overview", "postgres", "database-seeds", "prisma"]
},
{
"title": {
"title": "CLI",
"iconPath": "/img/terminal-purple.svg",
"iconDarkPath": "/img/terminal-white.svg"
},
"pages": [
"cli-overview",
"cli-new",
"cli-dev",
"cli-start",
"cli-build",
"cli-export",
"cli-prisma",
"cli-generate",
"cli-codegen",
"cli-routes"
]
},
{
"title": {
"title": "Templates",
"iconPath": "/img/template-purple.svg",
"iconDarkPath": "/img/template-white.svg"
},
"pages": ["templates"]
}
]

View File

@@ -0,0 +1,10 @@
.bar-of-progress::after {
content: "";
display: block;
position: absolute;
right: 0;
width: 100px;
height: 100%;
box-shadow: 0 0 10px currentColor, 0 0 5px currentColor;
transform: rotate(3deg) translate(0, -4px);
}

View File

@@ -0,0 +1,5 @@
@tailwind base;
abbr[title] {
text-decoration: none;
}

View File

@@ -0,0 +1,402 @@
.DocSearch--active {
overflow: hidden !important;
}
.DocSearch-Container {
height: 100vh;
left: 0;
position: fixed;
top: 0;
width: 100vw;
z-index: 200;
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.25);
padding: 1rem;
}
@screen sm {
.DocSearch-Container {
padding: 1.5rem;
}
}
@screen md {
.DocSearch-Container {
padding: 10vh;
}
}
@screen lg {
.DocSearch-Container {
padding: 12vh;
}
}
.DocSearch-LoadingIndicator svg {
display: none;
}
.DocSearch-LoadingIndicator {
display: none;
width: 1.5rem;
height: 1.5rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Ccircle cx='12' cy='12' r='9' stroke-width='2' stroke='%23cffafe' /%3E%3Cpath d='M3,12a9,9 0 1,0 18,0a9,9 0 1,0 -18,0' stroke-width='2' stroke='%2306b6d4' stroke-dasharray='56.5486677646' stroke-dashoffset='37.6991118431' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-size: 100% 100%;
}
.DocSearch-Container--Stalled .DocSearch-LoadingIndicator {
display: block;
}
.DocSearch-Modal {
margin: 0 auto;
width: 100%;
max-width: 47.375rem;
display: flex;
flex-direction: column;
min-height: 0;
border-radius: 1rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
background: white;
}
.DocSearch-SearchBar {
flex: none;
border-bottom: 1px solid theme("colors.gray.200");
position: relative;
z-index: 1;
display: flex;
align-items: center;
margin: 0 1.5rem;
}
.DocSearch-Form {
flex: auto;
display: flex;
align-items: center;
min-width: 0;
}
.DocSearch-Dropdown {
flex: auto;
border-bottom-left-radius: 1rem;
border-bottom-right-radius: 1rem;
padding: 0 1.5rem 1.5rem;
overflow: auto;
}
.DocSearch-MagnifierLabel {
flex: none;
width: 1.5rem;
height: 1.5rem;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z' stroke='%2306b6d4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
}
.DocSearch-MagnifierLabel svg {
display: none;
}
.DocSearch-Container--Stalled .DocSearch-MagnifierLabel {
display: none;
}
.DocSearch-Input {
appearance: none;
background: transparent;
height: 4.5rem;
font-size: 1rem;
font-weight: 500;
color: black;
margin-left: 1rem;
margin-right: 1rem;
flex: auto;
min-width: 0;
}
.DocSearch-Input:focus {
outline: 2px dotted transparent;
}
.DocSearch-Input::-webkit-search-cancel-button,
.DocSearch-Input::-webkit-search-decoration,
.DocSearch-Input::-webkit-search-results-button,
.DocSearch-Input::-webkit-search-results-decoration {
display: none;
}
.DocSearch-Reset {
display: none;
}
.DocSearch-Reset::before {
content: "esc";
}
.DocSearch-Cancel {
flex: none;
font-size: 0;
border-radius: 0.375rem;
background-color: theme("colors.gray.50");
border: 1px solid theme("colors.gray.300");
padding: 0.125rem 0.375rem;
}
.DocSearch-Cancel::before {
content: "esc";
color: theme("colors.gray.400");
font-size: 0.875rem;
line-height: 1.25rem;
}
.DocSearch-Reset svg {
display: none;
}
.DocSearch-Hit-source {
line-height: 1.5rem;
font-weight: bold;
color: theme("colors.gray.600");
margin-top: 1.5rem;
margin-bottom: 1rem;
}
.DocSearch-Hit-Container {
display: flex;
align-items: center;
height: 4rem;
}
.DocSearch-Hit-Tree {
display: none;
}
.DocSearch-Hit-icon {
flex: none;
margin-right: 0.875rem;
}
.DocSearch-Hit-icon path {
stroke-width: 2px;
stroke: #71717a;
}
.DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-icon path {
stroke: white;
}
.DocSearch-Hit-content-wrapper {
flex: auto;
display: flex;
flex-direction: column-reverse;
min-width: 0;
}
.DocSearch-Hit-path {
font-size: 0.75rem;
line-height: 1rem;
font-weight: 500;
color: theme("colors.gray.500");
}
.DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-path {
color: theme("colors.blue.light");
}
.DocSearch-Hit-title {
color: black;
line-height: 1.5rem;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-title {
color: white;
}
.DocSearch-Hit-title + .DocSearch-Hit-path {
margin-bottom: 0.125rem;
}
.DocSearch-Hit-action {
flex: none;
margin-left: 0.875rem;
}
.DocSearch-Hit-action-button {
display: flex;
}
.DocSearch-Hit-action + .DocSearch-Hit-action {
margin-left: 0.5rem;
}
.DocSearch-Hit-action path {
stroke-width: 2px;
stroke: #71717a;
}
.DocSearch-Hit[aria-selected="true"] .DocSearch-Hit-action path {
stroke: white;
}
.DocSearch-Hit > a {
display: block;
background: theme("colors.gray.50");
border-radius: 0.5rem;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
padding: 0 1.25rem 0 1rem;
}
.DocSearch-Hit[aria-selected="true"] > a {
background: theme("colors.purple.light");
}
.DocSearch-Hit + .DocSearch-Hit {
margin-top: 0.5rem;
}
.DocSearch-Hit {
position: relative;
}
.DocSearch-Hit--Child {
padding-left: 1.75rem;
}
.DocSearch-Hit--Child::before,
.DocSearch-Hit--Child + .DocSearch-Hit:not(.DocSearch-Hit--Child)::before {
content: "";
position: absolute;
top: -0.25rem;
bottom: -0.25rem;
left: 0.5rem;
width: 1.25rem;
background-image: url("data:image/svg+xml,%3Csvg width='12' height='200' viewBox='0 0 12 200' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 1 0 V 200 M 1 100 H 12' stroke='%23a1a1aa' stroke-width='2'/%3E%3C/svg%3E%0A");
background-repeat: no-repeat;
background-position: center left;
}
.DocSearch-Hit--Child:last-child::before,
.DocSearch-Hit--Child + .DocSearch-Hit:not(.DocSearch-Hit--Child)::before {
background-image: url("data:image/svg+xml,%3Csvg width='12' height='200' viewBox='0 0 12 200' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 1 0 V 89 Q 1 100 12 100' stroke='%23a1a1aa' stroke-width='2'/%3E%3C/svg%3E%0A");
}
.DocSearch-Hit:not(.DocSearch-Hit--Child) + .DocSearch-Hit--Child::after {
content: "";
position: absolute;
top: -0.25rem;
left: 0;
width: 1.25rem;
height: 0.25rem;
background: #fff;
}
.DocSearch-Hit--Child + .DocSearch-Hit:not(.DocSearch-Hit--Child)::before {
top: auto;
bottom: calc(100% + 0.25rem);
height: calc(100% + 0.25rem);
background-color: #fff;
}
.DocSearch-Hits mark {
background: none;
color: theme("colors.purple.light");
}
.DocSearch-Hit[aria-selected="true"] mark {
color: inherit;
text-decoration: underline;
}
.DocSearch-Footer {
flex: none;
display: flex;
justify-content: flex-end;
margin: 0 1.5rem;
border-top: 1px solid theme("colors.gray.200");
padding: 1.25rem 0;
}
.DocSearch-Commands {
display: none;
}
.DocSearch-Logo a {
display: flex;
align-items: center;
color: #5d6494;
font-size: 0.75rem;
font-weight: 500;
}
.DocSearch-Logo svg {
color: #5468ff;
margin-left: 0.5rem;
}
.DocSearch-Hit--deleting,
.DocSearch-Hit--favoriting {
opacity: 0;
transition: all 250ms linear;
}
.DocSearch-NoResults .DocSearch-Screen-Icon {
display: none;
}
.DocSearch-Title {
font-size: theme("fontSize.lg");
line-height: theme("lineHeight.6");
margin-bottom: 2.5rem;
}
.DocSearch-Title strong {
color: theme("colors.gray.900");
font-weight: 500;
}
.DocSearch-StartScreen,
.DocSearch-NoResults {
padding-top: 2.5rem;
padding-bottom: 1rem;
}
.DocSearch-StartScreen .DocSearch-Help {
font-size: theme("fontSize.lg");
line-height: theme("lineHeight.6");
}
.DocSearch-NoResults-Prefill-List .DocSearch-Help {
font-size: theme("fontSize.xs");
line-height: theme("lineHeight.4");
letter-spacing: theme("letterSpacing.wide");
text-transform: uppercase;
font-weight: 600;
padding-bottom: 0.5rem;
border-bottom: 1px solid theme("colors.gray.200");
}
.DocSearch-NoResults-Prefill-List li {
padding: 0.5rem 0;
border-bottom: 1px solid theme("colors.gray.200");
}
.DocSearch-NoResults-Prefill-List button {
font-weight: 500;
color: theme("colors.purple.light");
}
.DocSearch-NoResults-Prefill-List + .DocSearch-Help {
font-size: theme("fontSize.sm");
line-height: theme("lineHeight.5");
margin-top: 1rem;
}
.DocSearch-NoResults-Prefill-List + .DocSearch-Help a {
box-shadow: theme("boxShadow.link");
color: theme("colors.purple.light");
font-weight: 500;
}

View File

@@ -0,0 +1,146 @@
@import "base.css";
/*! purgecss start ignore */
@import "docsearch.css";
@import "prism.css";
@import "bar-of-progress.css";
/*! purgecss end ignore */
@tailwind components;
@import "utilities.css";
html {
font-family: "Libre Franklin", sans-serif;
scroll-behavior: smooth;
}
.player-wrapper {
position: relative;
padding-top: 56.25%;
}
.react-player {
position: absolute;
top: 0;
left: 0;
}
.background-to-video {
margin-top: calc((100vw - 3rem) * (-9 / 16) * 0.5);
}
.features-grid {
grid-template-columns: repeat(4, 70vw);
}
.icon {
width: 0.9375rem;
height: 0.9375rem;
}
.icon-large {
width: 1rem;
height: 1rem;
}
.icon-background {
width: 1.5625rem;
height: 1.5625rem;
}
.icon-expanded .icon-background {
width: 2.5rem;
height: 2.5rem;
}
.icon-expanded .icon {
width: 1.5rem;
height: 1.5rem;
}
.architecture-diagram {
width: 800px;
}
.hand {
pointer-events: none;
--color-stop: theme("colors.purple.light");
--page-bg-color: theme("colors.white");
}
.dark .hand {
--color-stop: theme("colors.purple.dark");
--page-bg-color: theme("colors.black");
}
@screen sm {
.features-grid {
grid-template-columns: repeat(4, 25rem);
}
.architecture-diagram {
width: 1000px;
}
}
@screen md {
.architecture-diagram {
width: 1230px;
}
}
@screen lg {
.icon {
width: 1rem;
height: 1rem;
}
.icon-background {
width: 2.25rem;
height: 2.25rem;
}
.icon-large {
width: 1.4rem;
height: 1.4rem;
}
.background-to-video {
margin-top: calc((100vw - 4.25rem) * (-9 / 16) * 0.25);
}
.features-grid {
grid-template-columns: repeat(2, 1fr);
}
.architecture-diagram {
width: 100%;
}
}
@screen xl {
.background-to-video {
margin-top: calc((1280px - 4.25rem) * (-9 / 16) * 0.25);
}
.features-grid {
grid-template-columns: repeat(2, 1fr);
}
}
code::before,
code::after {
content: "" !important;
}
code {
@apply rounded-sm;
padding: 2px 5px;
}
.hide-scrollbar {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
}
.hide-scrollbar::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}

View File

@@ -0,0 +1,126 @@
.token.tag,
.token.class-name,
.token.selector,
.token.selector .class,
.token.function {
@apply text-purple-light;
}
.dark .token.tag,
.dark .token.class-name,
.dark .token.selector,
.dark .token.selector .class,
.dark .token.function {
@apply text-purple-extralight;
}
.token.attr-name,
.token.keyword,
.token.rule,
.token.operator,
.token.pseudo-class,
.token.important {
@apply text-purple-light;
}
.dark .token.attr-name,
.dark .token.keyword,
.dark .token.rule,
.dark .token.operator,
.dark .token.pseudo-class,
.dark .token.important {
@apply text-purple-extralight;
}
.token.attr-value,
.token.class,
.token.string,
.token.number,
.token.unit,
.token.color {
@apply text-black;
}
.dark .token.attr-value,
.dark .token.class,
.dark .token.string,
.dark .token.number,
.dark .token.unit,
.dark .token.color {
@apply text-white;
}
.token.punctuation,
.token.module,
.token.property {
@apply text-purple-mid;
}
.dark .token.punctuation,
.dark .token.module,
.dark .token.property {
@apply text-purple-extralight;
}
.token.atapply .token:not(.rule):not(.important) {
color: inherit;
}
.language-shell .token:not(.comment) {
color: inherit;
}
.language-css .token.function {
color: inherit;
}
.token.comment {
@apply text-gray-600;
}
.token.deleted:not(.prefix) {
@apply relative block px-4 -mx-4;
}
.token.deleted:not(.prefix)::after {
content: "";
@apply absolute inset-0 block bg-opacity-25 pointer-events-none bg-rose-400;
}
.token.deleted.prefix {
@apply text-gray-400 select-none;
}
.token.inserted:not(.prefix) {
@apply block bg-green-600 bg-opacity-25 px-4 -mx-4;
}
.token.inserted.prefix {
@apply text-green-600 text-opacity-75 select-none;
}
.token.inserted .token.comment {
@apply text-blue-dark;
}
.token.highlighted:not(.prefix) {
@apply block bg-purple-light bg-opacity-25 -mx-4 px-4 -mb-4;
}
.token.highlighted:not(.prefix).not(:first-child) {
margin-top: -1.5em;
}
.token.highlighted:not(.prefix).not(:last-child) {
margin-bottom: -1.5em;
}
.token.highlighted:not(.prefix) > .comment:first-child {
height: 0;
display: block;
overflow: hidden;
margin-top: 0rem;
margin-bottom: -1.4em;
}
.token.highlighted.highlighted-line {
@apply -mx-6;
}

View File

@@ -0,0 +1,184 @@
@tailwind utilities;
.scrollbar-none {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none !important;
}
}
.scrollbar-w-2::-webkit-scrollbar {
@apply w-2 h-2 !important;
}
.scrollbar-track-gray-lighter::-webkit-scrollbar-track {
@apply bg-gray-300 !important;
}
.scrollbar-thumb-gray::-webkit-scrollbar-thumb {
@apply bg-gray-400 !important;
}
.scrollbar-thumb-rounded::-webkit-scrollbar-thumb {
@apply rounded !important;
}
@supports (position: sticky) {
@media (min-width: theme("screens.lg")) {
.sticky\?lg\:h-screen {
height: 100vh !important;
}
.sticky\?lg\:h-\(screen-18\) {
height: calc(100vh - theme("spacing.18"));
}
}
}
@media (prefers-reduced-motion: reduce) {
.motion-reduce\:transform-none:hover {
transform: none !important;
}
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe\:hover\:-translate-y-1:hover {
--transform-translate-y: -0.25rem !important;
}
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe\:hover\:scale-110:hover {
--transform-scale-x: 1.1 !important;
--transform-scale-y: 1.1 !important;
}
}
.focus\:bg-gray-600:focus {
@apply bg-gray-600 !important;
}
.focus\:text-white:focus {
@apply text-white !important;
}
.group:hover .group-hover\:text-white {
@apply text-white !important;
}
.focus-within\:border-teal-500:focus-within {
@apply border-teal-500 !important;
}
.focus-visible\:underline:focus-visible {
@apply underline !important;
}
.focus-visible\:underline.focus-visible {
@apply underline !important;
}
.active\:bg-blue-700:active {
@apply bg-blue-700 !important;
}
.checked\:bg-blue-600:checked {
@apply bg-blue-600 !important;
}
.checked\:border-transparent:checked {
@apply border-transparent !important;
}
.appearance-none::-ms-expand {
display: none !important;
}
.bg-checkered {
background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23F0F0F0' d='M0 0h8v8H0zm8 8h8v8H8z'/%3E%3C/svg%3E");
background-size: 16px 16px;
}
.after\:hash::after {
content: "#";
}
.code-highlight {
border-radius: 0.1875rem;
padding: 0.0625rem 0.1875rem;
margin: 0 -0.1875rem;
}
body.cursor-grabbing * {
cursor: grabbing !important;
}
.mono-active > div:not(.not-mono) > span {
color: rgba(255, 255, 255, 0.25);
}
.mono > div > span {
transition-duration: 0.5s;
transition-property: background-color, border-color, color, fill, stroke;
}
.form-tick:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e");
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.topic-select .topic-select__placeholder,
.topic-select .topic-select__menu-list,
.topic-select .topic-select__single-value {
@apply text-black dark:text-dark-mode-text font-medium;
}
.topic-select .topic-select__menu,
.topic-select .topic-select__control {
@apply rounded-none border border-blue-light dark:border-purple-off-black;
}
.topic-select .topic-select__control,
.topic-select .topic-select__menu-list {
@apply dark:bg-purple-off-black;
}
.topic-select .topic-select__control {
@apply py-1 px-2 shadow-none;
}
.topic-select .topic-select__option {
@apply py-3 px-4;
}
.topic-select .topic-select__menu-list {
@apply py-2;
}
.topic-select .topic-select__menu {
@apply shadow-none;
}
.topic-select .topic-select__indicator-separator {
@apply hidden;
}
.topic-select .topic-select__dropdown-indicator {
@apply flex-wrap flex w-7;
}
.showcase-block {
@apply cursor-pointer w-full p-2;
}
.showcase-block div {
@apply w-full rounded-md shadow-md transition ease-in-out duration-300;
}
.showcase-block:hover div {
@apply shadow-xl transform -translate-y-2;
}
.showcase-block:hover .showcase-block-title {
@apply underline text-purple-light dark:text-purple-extralight;
}

View File

@@ -0,0 +1,15 @@
export async function getGitHubFile({octokit, owner, repo, path, json = false}) {
const {data: file} = await octokit.repos.getContent({
owner,
repo,
path,
})
if (Array.isArray(file) || !("content" in file)) {
throw new Error(`"${path} isn't a file in "${owner}/${repo}`)
}
const rawContent = Buffer.from(file.content, "base64").toString("utf-8")
return json ? JSON.parse(rawContent) : rawContent
}

View File

@@ -0,0 +1,78 @@
const showcaseList = [
{
thumbnail: "/showcase/blitz.png",
title: "Blitz.js Homepage",
URL: "https://blitzjs.com/",
},
{
thumbnail: "/showcase/budg.png",
title: "Budg",
URL: "https://budg.co/",
},
{
thumbnail: "/showcase/dweet.png",
title: "Dweet",
URL: "https://www.dweet.com/",
},
{
thumbnail: "/showcase/eminate.png",
title: "Eminate",
URL: "https://www.eminate.io/",
},
{
thumbnail: "/showcase/fungarzione.png",
title: "Glink",
URL: "https://glink.so/",
},
{
thumbnail: "/showcase/git-nation.png",
title: "GitNation Portal",
URL: "https://portal.gitnation.org/",
},
{
thumbnail: "/showcase/hopscotch-product-tours.png",
title: "Hopscotch Product Tours",
URL: "https://hopscotch.club/",
},
{
thumbnail: "/showcase/hyperlocal.png",
title: "Hyperlocal",
URL: "https://hyperlocal.builtforfifty.com/",
},
{
thumbnail: "/showcase/long-term-visas.png",
title: "Long term visas",
URL: "https://longtermvisas.com/",
},
{
thumbnail: "/showcase/lure-masters.png",
title: "Lure Masters",
URL: "https://www.luremasters.net/",
},
{
thumbnail: "/showcase/quirrel.png",
title: "Quirrel",
URL: "https://quirrel.dev/",
},
{
thumbnail: "/showcase/tansy.png",
title: "Tansy",
URL: "https://www.tansy.co/",
},
{
thumbnail: "/showcase/topodotink-map-posters.png",
title: "Topo.ink",
URL: "https://www.topo.ink/",
},
{
thumbnail: "/showcase/placemark.png",
title: "Placemark",
URL: "https://www.placemark.io/",
},
{
thumbnail: "/showcase/hire-win.png",
title: "hire.win",
URL: "https://hire.win/",
},
]
export default showcaseList

4
website/babel.config.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
presets: ["next/babel"],
plugins: ["preval", "macros"],
}

5
website/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

220
website/next.config.mjs Normal file
View File

@@ -0,0 +1,220 @@
import bundleAnalyzer from "@next/bundle-analyzer"
import fs from "fs"
import {withBlitz} from "@blitzjs/next"
import matter from "gray-matter"
import minimatch from "minimatch"
import path from "path"
import querystring from "querystring"
import {createLoader} from "simple-functional-loader"
import {withBlitzLinks} from "./remark/withBlitzLinks.mjs"
import {withProse} from "./remark/withProse.mjs"
import {withSyntaxHighlighting} from "./remark/withSyntaxHighlighting.mjs"
import {withTableOfContents} from "./remark/withTableOfContents.mjs"
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true",
})
/**
* @type {import('@blitzjs/next').BlitzConfig}
**/
const fallbackDefaultExports = {
"pages/docs/**/*": ["app/core/layouts/DocumentationLayout", "DocumentationLayout"],
}
const config = withBlitz(
withBundleAnalyzer({
pageExtensions: ["js", "jsx", "mdx"],
images: {
domains: [
"raw.githubusercontent.com",
"avatars.githubusercontent.com",
"avatars0.githubusercontent.com",
"avatars1.githubusercontent.com",
"avatars2.githubusercontent.com",
"avatars3.githubusercontent.com",
"avatars4.githubusercontent.com",
"avatars5.githubusercontent.com",
"avatars6.githubusercontent.com",
"media-exp1.licdn.com",
],
},
async redirects() {
return [
{
source: "/docs/getting-started",
destination: "/docs/get-started",
permanent: false,
},
{
source: "/joinmeetup",
destination: "https://us02web.zoom.us/j/85901497017?pwd=eVo4YlhsU2E3UHQvUmgxTmtRUDBIZz09",
permanent: false,
},
{
source: "/fame",
destination: "https://twitter.com/flybayer/status/1361334647859384320",
permanent: false,
},
{
source: "/discord",
destination: "http://discord.gg/blitzjs",
permanent: false,
},
]
},
async rewrites() {
return [
{
source: "/stickers",
destination: "/docs/stickers",
},
]
},
webpack(config, options) {
if (!options.dev) {
options.defaultLoaders.babel.options.cache = false
}
config.module.rules.push({
test: /\.(png|jpe?g|gif|webp)$/i,
use: [
{
loader: "file-loader",
options: {
publicPath: "/_next",
name: "static/media/[name].[hash].[ext]",
},
},
],
})
config.module.rules.push({
test: /\.svg$/,
use: [
{loader: "@svgr/webpack", options: {svgoConfig: {plugins: {removeViewBox: false}}}},
{
loader: "file-loader",
options: {
publicPath: "/_next",
name: "static/media/[name].[hash].[ext]",
},
},
],
})
config.module.rules.push({
test: /\.mdx$/,
use: [
options.defaultLoaders.babel,
createLoader(function (source) {
if (source.includes("/*START_META*/")) {
const [meta] = source.match(/\/\*START_META\*\/(.*?)\/\*END_META\*\//s)
return "export default " + meta
}
return (
source.replace(/^export const/gm, "const") +
`\nMDXContent.layoutProps = layoutProps\n`
)
}),
{
loader: "@mdx-js/loader",
options: {
remarkPlugins: [
withProse,
withTableOfContents,
withSyntaxHighlighting,
withBlitzLinks,
],
},
},
createLoader(function (source) {
console.log("hello", this.resourceQuery)
let {meta: fields} = querystring.parse(this.resourceQuery.substr(1))
let {data: meta, content: body} = matter(source)
if (fields) {
for (let field in meta) {
if (!fields.split(",").includes(field)) {
delete meta[field]
}
}
}
let extra = []
let resourcePath = path.relative(process.cwd(), this.resourcePath)
// If no custom layout, use the default layout
if (!/^\s*export\s+default\s+/m.test(source.replace(/```(.*?)```/gs, ""))) {
for (let glob in fallbackDefaultExports) {
if (minimatch(resourcePath, glob)) {
extra.push(
`import { ${fallbackDefaultExports[glob][1]} as _Default } from '${fallbackDefaultExports[glob][0]}'`,
"export default _Default",
)
break
}
}
}
// If there are any cards, impory the component
if (/^<\/Card>$/m.test(body)) {
extra.push(`import { Card } from 'app/core/components/docs/Card'`)
// Until MDX v2 is available, all content inside a component must
// have extra spaces. Here are added just in case.
// https://mdxjs.com/guides/markdown-in-components
// body = body
// .replace(/<Card .+?>\n*/g, (tag) => tag.trimEnd() + "\n\n")
// .replace(/\n*<\/Card>/g, (tag) => "\n\n" + tag.trimStart())
}
return [
...(typeof fields === "undefined" ? extra : []),
typeof fields === "undefined" ? body : "",
typeof fields === "undefined"
? `export const meta = ${JSON.stringify(meta)}`
: `export const meta = /*START_META*/${JSON.stringify(meta || {})}/*END_META*/`,
].join("\n\n")
}),
],
})
config.module.rules.push({
test: /navs[/\\]documentation\.json$/, // accept both navs/documentation.json and navs\documentation.json
use: [
createLoader(function (source) {
const documentation = JSON.parse(source)
let finalDocumentation = []
for (const category of documentation) {
let pages = []
for (const page of category.pages) {
const pageFile = fs.readFileSync(
path.resolve(process.cwd(), "pages", "docs", `${page}.mdx`),
{encoding: "utf-8"},
)
const {data} = matter(pageFile)
pages.push({
...data,
href: `/docs/${page}`,
})
}
finalDocumentation.push({
...category,
pages,
})
}
return JSON.stringify(finalDocumentation)
}),
],
})
return config
},
}),
)
export default config

78
website/package.json Normal file
View File

@@ -0,0 +1,78 @@
{
"name": "blitz-site-v2",
"version": "1.0.0",
"private": true,
"volta": {
"node": "14.18.1",
"yarn": "1.22.17"
},
"scripts": {
"start:dev": "next dev",
"clean": "rm -rf node_modules && rm -rf .turbo && rm -rf .next",
"start": "next start",
"build": "next build",
"lint": "pnpm lint:code && pnpm lint:docs",
"lint:code": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"lint:docs": "alex pages/docs/**/*.mdx",
"english-slugify": "node scripts/english-slugify.mjs"
},
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix"
]
},
"dependencies": {
"@blitzjs/config": "workspace:*",
"@blitzjs/next": "workspace:*",
"@docsearch/react": "3.0.0-alpha.42",
"@mdx-js/loader": "1.6.22",
"@octokit/rest": "18.12.0",
"@reach/rect": "0.16.0",
"@sindresorhus/slugify": "2.1.0",
"@visx/hierarchy": "2.1.2",
"@visx/responsive": "2.4.1",
"blitz": "workspace: *",
"clsx": "1.1.1",
"fathom-client": "3.2.0",
"focus-visible": "5.2.0",
"gray-matter": "4.0.3",
"next": "12.2.5",
"next-themes": "0.0.15",
"prismjs": "1.29.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "4.3.1",
"react-player": "2.9.0",
"react-select": "5.2.1",
"remark-custom-heading-id": "1.0.0",
"ts-node": "10.7.0",
"typeface-libre-franklin": "1.1.13",
"typeface-roboto": "1.1.13",
"typeface-roboto-mono": "1.1.13",
"typescript": "4.5.2",
"unist-util-visit": "2.0.3"
},
"devDependencies": {
"@babel/generator": "7.19.0",
"@babel/parser": "7.19.1",
"@next/bundle-analyzer": "11.1.2",
"@svgr/webpack": "6.1.1",
"@tailwindcss/typography": "0.4.1",
"alex": "10.0.0",
"autoprefixer": "10.4.0",
"babel-plugin-macros": "3.1.0",
"babel-plugin-preval": "5.0.0",
"eslint": "7.32.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"file-loader": "6.2.0",
"husky": "7.0.4",
"lint-staged": "12.1.2",
"minimatch": "3.0.4",
"postcss": "8.4.4",
"postcss-nested": "5.0.6",
"prettier": "2.5.1",
"pretty-quick": "3.1.2",
"simple-functional-loader": "1.2.1",
"tailwindcss": "2.2.19"
}
}

56
website/pages/_app.js Normal file
View File

@@ -0,0 +1,56 @@
import "typeface-libre-franklin"
import "typeface-roboto"
import "typeface-roboto-mono"
import "@/styles/main.css"
import "focus-visible"
import * as Fathom from "fathom-client"
import Head from "next/head"
import {ThemeProvider} from "next-themes"
import {useEffect} from "react"
import {Title} from "@/components/Title"
export default function App({Component, pageProps, router}) {
const meta = Component.meta || {}
useEffect(() => {
// Initialize Fathom when the app loads
Fathom.load("NGIOZUKS", {
includedDomains: ["blitzjs.com"],
excludedDomains: ["canary.blitzjs.com", "legacy.blitzjs.com", "alpha.blitzjs.com"],
})
function onRouteChangeComplete() {
Fathom.trackPageview()
if (typeof window !== "undefined") {
window.scrollTo(0, 0)
}
}
// Record a pageview when route changes
router.events.on("routeChangeComplete", onRouteChangeComplete)
// Unassign event listener
return () => {
router.events.off("routeChangeComplete", onRouteChangeComplete)
}
}, [router.events])
return (
<>
<Title>{meta.metaTitle || meta.title}</Title>
<Head>
<meta key="twitter:card" name="twitter:card" content="summary_large_image" />
<meta key="twitter:site" name="twitter:site" content="@blitz_js" />
<meta key="twitter:description" name="twitter:description" content={meta.description} />
<meta key="twitter:creator" name="twitter:creator" content="@blitz_js" />
<meta key="og:url" property="og:url" content={`https://blitzjs.com${router.pathname}`} />
<meta key="og:type" property="og:type" content="article" />
<meta key="og:description" property="og:description" content={meta.description} />
</Head>
<ThemeProvider defaultTheme="dark" enableSystem={false} attribute="class">
<Component {...pageProps} />
</ThemeProvider>
</>
)
}

View File

@@ -0,0 +1,29 @@
import Document, { Html, Main, NextScript, Head } from "next/document"
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return {...initialProps}
}
render() {
return (
<Html lang="en" className="antialiased">
<Head>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="128x128" href="/128x128-Favicon-Purple.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/32x32-Favicon-Purple.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/16x16-Favicon-Purple.png" />
<link rel="manifest" href="/manifest.json" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5600C2" />
<meta name="theme-color" content="#ffffff" />
</Head>
<body className="text-black bg-white dark:bg-black dark:text-dark-mode-text">
<Main />
<NextScript />
</body>
</Html>
)
}
}

View File

@@ -0,0 +1,359 @@
---
title: API Routes
sidebar_label: API Routes
---
<Card type="info">
Unlike Next.js, your `api/` folder should be a sibling of `pages/` instead
of being nested inside. But `pages/api` is still supported for
compatibility with Next.js.
</Card>
Any file inside an `api/` folder are accessible at a URL corresponding to
its path inside `api/`. So `app/projects/api/webhook.ts` will be at
`localhost:3000/api/webhook`.
For example, the following API route `app/api/hello.js` handles a `json`
response:
```ts
const handler = (req: BlitzApiRequest, res: BlitzApiResponse) => {
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({name: "John Doe"}))
}
export default handler
```
For an API route to work, you need to export as default a function (a.k.a
**request handler**), which then receives the following parameters:
- `req: BlitzApiRequest`: An instance of
[http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage),
plus some pre-built middlewares you can see [here](#default-middlewares)
- `res: BlitzApiResponse`: An instance of
[http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse),
plus some helper functions you can see [here](#response-helpers)
If you want to avoid writing the types `BlitzApiRequest` and
`BlitzApiResponse` everywhere, you can use `BlitzApiHandler`:
```ts
const handler: BlitzApiHandler = (req, res) => {
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({name: "John Doe"}))
}
export default handler
```
To handle different HTTP methods in an API route, you can use `req.method`
in your request handler, like so:
```ts
const handler: BlitzApiHandler = (req, res) => {
if (req.method === "POST") {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
export default handler
```
To fetch API endpoints, take a look into any of the examples at the start
of this section.
> 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#connectexpress-middleware-support).
> API Routes do not increase your client-side bundle size. They are
> server-side only bundles.
## Authentication and Session Context {#authentication-and-session-context}
You can use the `getSession` function to get the session of the user. Here
is an example using session in a API route `app/api/customRoute.tsx`:
```ts
import {getSession, BlitzApiRequest, BlitzApiResponse} from "blitz"
export default async function customRoute(req: BlitzApiRequest, res: BlitzApiResponse) {
const session = await getSession(req, res)
console.log("User ID:", session.userId)
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({userId: session.userId}))
}
```
This is called in the frontend like this:
```ts
import {getAntiCSRFToken} from "blitz"
const antiCSRFToken = getAntiCSRFToken()
const response = await window.fetch("/api/customRoute", {
credentials: "include",
headers: {
"anti-csrf": antiCSRFToken,
},
})
```
[Read more about Session Management](/docs/session-management)
## Dynamic API Routes {#dynamic-api-routes}
Dynamic API routes follow the same file naming rules used for `pages`.
For example, the API route `app/api/post/[pid].js` has the following code:
```ts
const handler: BlitzApiHandler = (req, res) => {
const {
query: {pid},
} = req
res.end(`Post: ${pid}`)
}
export default handler
```
Now, a request to `/api/post/abc` will respond with the text: `Post: abc`.
#### Index routes and Dynamic API routes
A very common RESTful pattern is to set up routes like this:
- `GET api/posts/` - gets a list of posts, probably paginated
- `GET api/posts/12345` - gets post id 12345
We can model this in two ways:
- Option 1:
- `/api/posts.js`
- `/api/posts/[postId].js`
- Option 2:
- `/api/posts/index.js`
- `/api/posts/[postId].js`
Both are equivalent.
#### Catch all API routes
API Routes can be extended to catch all paths by adding three dots (`...`)
inside the brackets. For example:
- `app/api/post/[...slug].js` matches `/api/post/a`, but also
`/api/post/a/b`, `/api/post/a/b/c` and so on.
> **Note**: You can use names other than `slug`, such as: `[...param]`
Matched parameters will be passed as a query parameter (`slug` in the
example) to the api handler, and it will always be an array, so, the path
`/api/post/a` will have the following `query` object:
```json
{"slug": ["a"]}
```
And in the case of `/api/post/a/b`, and any other matching path, new
parameters will be added to the array, like so:
```json
{"slug": ["a", "b"]}
```
An API route for `app/api/post/[...slug].js` could look like this:
```ts
const handler: BlitzApiHandler = (req, res) => {
const {
query: {slug},
} = req
res.end(`Post: ${slug.join(", ")}`)
}
export default handler
```
Now, a request to `/api/post/a/b/c` will respond with the text:
`Post: a, b, c`.
#### Optional catch all API routes
Catch all routes can be made optional by including the parameter in double
brackets (`[[...slug]]`).
For example, `app/api/post/[[...slug]].js` will match `/api/post`,
`/api/post/a`, `/api/post/a/b`, and so on.
The `query` objects are as follows:
```json
{ } // GET `/api/post` (empty object)
{ "slug": ["a"] } // `GET /api/post/a` (single-element array)
{ "slug": ["a", "b"] } // `GET /api/post/a/b` (multi-element array)
```
#### Caveats
- Predefined API routes take precedence over dynamic API routes, and
dynamic API routes over catch all API routes. Take a look at the
following examples:
- `app/api/post/create.js` - Will match `/api/post/create`
- `app/api/post/[pid].js` - Will match `/api/post/1`, `/api/post/abc`,
etc. But not `/api/post/create`
- `app/api/post/[...slug].js` - Will match `/api/post/1/2`,
`/api/post/a/b/c`, etc. But not `/api/post/create`, `/api/post/abc`
## Response Helpers {#response-helpers}
The response (`res`) includes a set of Express.js-like methods to improve
the developer experience and increase the speed of creating new API
endpoints, take a look at the following example:
```ts
const handler: BlitzApiHandler = (req, res) => {
res.status(200).json({name: "Blitz.js"})
}
export default handler
```
The included helpers are:
- `res.status(code)` - A function to set the status code. `code` must be a
valid
[HTTP status code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)
- `res.json(body)` - Sends a JSON response. `body` must be a valid
[serialiazable object](https://developer.mozilla.org/en-US/docs/Glossary/Serialization)
- `res.send(body)` - Sends the HTTP response. `body` can be a `string`, an
`object` or a `Buffer`
## Default Middlewares {#default-middlewares}
API routes provide built in middlewares which parse the incoming request
(`req`). Those middlewares are:
- `req.cookies` - An object containing the cookies sent by the request.
Defaults to `{}`
- `req.query` - An object containing the
[query string](https://en.wikipedia.org/wiki/Query_string). Defaults to
`{}`
- `req.body` - An object containing the body parsed by `content-type`, or
`null` if no body was sent
### Custom config {#custom-config}
Every API route can export a `config` object to change the default
configs, which are the following:
```ts
export const config = {
api: {
bodyParser: {
sizeLimit: "1mb",
},
},
}
```
The `api` object includes all configs available for API routes.
`bodyParser` enables body parsing, you can disable it if you want to
consume it as a `Stream`:
```ts
export const config = {
api: {
bodyParser: false,
},
}
```
`bodyParser.sizeLimit` is the maximum size allowed for the parsed body, in
any format supported by [bytes](https://github.com/visionmedia/bytes.js),
like so:
```ts
export const config = {
api: {
bodyParser: {
sizeLimit: "500kb",
},
},
}
```
`externalResolver` is an explicit flag that tells the server that this
route is being handled by an external resolver like _express_ or
_connect_. Enabling this option disables warnings for unresolved requests.
```ts
export const config = {
api: {
externalResolver: true,
},
}
```
### Connect/Express middleware support {#connect-express-middleware-support}
You can also use [Connect](https://github.com/senchalabs/connect)
compatible middleware.
For example,
[configuring CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
for your API endpoint can be done leveraging the
[cors](https://www.npmjs.com/package/cors) package.
First, install `cors`:
```bash
npm i cors
# or
yarn add cors
```
Now, let's add `cors` to the API route:
```ts
import Cors from "cors"
// Initializing the cors middleware
const cors = Cors({
methods: ["GET", "HEAD"],
})
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
async function handler(req: BlitzApiRequest, res: BlitzApiResponse) {
// Run the middleware
await runMiddleware(req, res, cors)
// Rest of the API logic
res.json({message: "Hello Everyone!"})
}
export default handler
```

View File

@@ -0,0 +1,46 @@
---
title: Root <App> Component
sidebar_label: <App>
---
Blitz uses the `App` component in `app/pages/_app.ts` to initialize pages.
You can modify it to do amazing things like:
- Persisting layout between page changes
- Keeping state when navigating pages
- Custom error handling using `componentDidCatch`
- Inject additional data into pages
- [Add global CSS](./css)
The default `_app.tsx` file looks like this:
```tsx
// app/pages/_app.tsx
export default function App({Component, pageProps}) {
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
```
The `Component` prop is the active `page`, so whenever you navigate
between routes, `Component` will change to the new `page`. Therefore, any
props you send to `Component` will be received by the `page`.
`pageProps` is an object with the initial props that were preloaded for
your page by one of our data fetching methods like
[`getStaticProps`](./get-static-props) or
[`getServerSideProps`](./get-server-side-props), otherwise it's an empty
object.
### Caveats {#caveats}
- Adding a custom `getInitialProps` in your `App` will disable
[Automatic Static Optimization](./pages#automatic-static-optimization)
in pages without [Static Generation](./get-static-props).
- When you add `getInitialProps` in your custom app, you must
`import App from "blitz"`, call `App.getInitialProps(appContext)` inside
`getInitialProps` and merge the returned object into the return value.
- `App` currently does not support page data fetching methods like
[`getStaticProps`](./get-static-props) or
[`getServerSideProps`](./get-server-side-props).

View File

@@ -0,0 +1,96 @@
---
title: Setup
sidebar_label: Setup
---
### Blitz Auth with Next.js {#setup-nextjs}
Install `@blitzjs/auth` plugin with:
```bash
npm i @blitzjs/auth # yarn add @blitzjs/auth # pnpm add @blitzjs/auth
```
#### Server setup {#server-setup}
Add the following to the `blitz-server.ts` file:
```typescript
import {setupBlitzServer} from "@blitzjs/next"
import {AuthServerPlugin, PrismaStorage, simpleRolesIsAuthorized} from "@blitzjs/auth"
import {db} from "db"
const {gSSP, gSP, api} = setupBlitzServer({
plugins: [
AuthServerPlugin({
cookiePrefix: "blitz-app-prefix",
storage: PrismaStorage(db),
isAuthorized: simpleRolesIsAuthorized,
}),
],
})
export {gSSP, gSP, api}
```
The `storage` property is needed to handle session and user storage. You
can use `PrismaStorage` and pass your Prisma client as in the example
above. You can also define the storage methods yourself:
```typescript
import {setupBlitzServer} from "@blitzjs/next"
import {AuthServerPlugin, PrismaStorage, simpleRolesIsAuthorized} from "@blitzjs/auth"
const {gSSP, gSP, api} = setupBlitzServer({
plugins: [
AuthServerPlugin({
cookiePrefix: "blitz-app-prefix",
isAuthorized: simpleRolesIsAuthorized,
storage: {
getSession: (handle) => redis.get(`session:${handle}`),
createSession: (session) => redis.set(`session:${handle}`, session),
deleteSession: (handle) => redis.del(`session:${handle}`),
// ...
},
}),
],
})
export {gSSP, gSP, api}
```
[**Here's a GitHub gist with a full Redis example.**](https://gist.github.com/beerose/80f37b4b36cbd7ba2745701959e3cb8b)
The storage methods need to conform to the following interface:
```typescript
interface SessionConfigMethods {
getSession: (handle: string) => Promise<SessionModel | null>
getSessions: (userId: PublicData["userId"]) => Promise<SessionModel[]>
createSession: (session: SessionModel) => Promise<SessionModel>
updateSession: (
handle: string,
session: Partial<SessionModel>,
) => Promise<SessionModel | undefined>
deleteSession: (handle: string) => Promise<SessionModel>
}
```
#### Client setup {#client-setup}
Then, add the following to your `blitz-client.ts` file:
```typescript
import {AuthClientPlugin} from "@blitzjs/auth"
import {setupBlitzClient} from "@blitzjs/next"
const {withBlitz} = setupBlitzClient({
plugins: [
AuthClientPlugin({
cookiePrefix: "web-cookie-prefix",
}),
],
})
export {withBlitz}
```

View File

@@ -0,0 +1,231 @@
---
title: Auth Hooks & Utilities
sidebar_label: Hooks & Utilities
---
## `useSession()` {#use-session}
`useSession(options?) => Partial<PublicData> & {isLoading: boolean}`
### Example {#example}
```ts
import {useSession} from "@blitzjs/auth"
const session = useSession()
```
### Arguments {#arguments}
- `options:`
- Optional
- `initialPublicData: PublicData` - Use this with SSR to set public data
from the server session
- `suspense: boolean` - Defaults to `true`
### Returns {#returns}
- `session: Partial<PublicData> & {isLoading: boolean}`
## `useAuthenticatedSession()` {#use-authenticated-session}
`useAuthenticatedSession(options?) => PublicData & {isLoading: boolean}`
This will throw `AuthenticationError` if the user is not logged in
### Example {#example-1}
```ts
import {useAuthenticatedSession} from "@blitzjs/auth"
const session = useAuthenticatedSession()
```
### Arguments {#arguments-1}
- `options:`
- Optional
- `initialPublicData: PublicData` - Use this with SSR to set public data
from the server session
- `suspense: boolean` - Defaults to `true`
### Returns {#returns-1}
- `session: PublicData & {isLoading: boolean}`
## `useAuthorize()` {#use-authorize}
`useAuthorize() => void`
This will throw `AuthenticationError` if the user is not logged in
### Example {#example-2}
```ts
import {useAuthorize} from "@blitzjs/auth"
useAuthorize()
```
### Arguments {#arguments-2}
- None
### Returns {#returns-2}
- Nothing
## `useRedirectAuthenticated()` {#use-redirect-authenticated}
`useRedirectAuthenticated(to: string) => void`
This will redirect a logged in user to the given url path. It does nothing
for logged out users.
### Example {#example-3}
```ts
import {useRedirectAuthenticated} from "@blitzjs/auth"
useRedirectAuthenticated("/dashboard")
```
### Arguments {#arguments-3}
- `to: string`
- **Required**
- The url path to redirect logged in users to
### Returns {#returns-3}
- Nothing
## `generateToken()` {#generate-token}
#### `generateToken(numberOfCharacters: number = 32) => string`
This is a convenience wrapper around
[nanoid](https://github.com/ai/nanoid) for generating tokens for things
like password resets.
#### Example Usage
```ts
import {generateToken} from "@blitzjs/auth"
const token = generateToken()
```
## `hash256()` {#hash256}
#### `hash256(value: string) => string`
This is a convenience wrapper that uses the node
[crypto](https://nodejs.org/api/crypto.html) module to hash a string with
the `sha256` algorithm. It is used for things like hashing password reset
tokens before saving them in the database.
Hash256 is also useful for storing strings like API keys in the database because the returned hash will always be the same for a given string. Therefore, you can still verify that an API key exists in the database when the only value you have to reference is the hashed key.
#### Example Usage
```ts
import {hash256} from "@blitzjs/auth"
const hashedToken = hash256(token)
```
## `SecurePassword` {#secure-password}
`SecurePassword` is a convenience wrapper around
[secure-password](https://github.com/emilbayes/secure-password) to provide
a nice way to hash passwords and verify password hashes.
```ts
import {SecurePassword} from "@blitzjs/auth"
await SecurePassword.hash(password)
await SecurePassword.verify(passwordHash, password)
```
#### `SecurePassword.hash(password: string) => Promise<string>`
This is used when a user sets a new password.
It takes a password string and returns a secure hash for storing in your
database.
`SecurePassword.hash` will return a different hash when given the same string, hence the necessity of `SecurePassword.verify` to compare hashes.
#### `SecurePassword.verify(passwordHash: string, password: string) => Promise<ResultCode>`
This is used when a user logs in to verify they used the correct password.
It takes a password hash from your database and the given password. It
will verify the given password is correct and return a result code, or if
incorrect, it will throw `AuthenticationError`.
##### Result Codes
**`SecurePassword.VALID`**
The password was verified and is valid
**`SecurePassword.VALID_NEEDS_REHASH`**
The password was verified and is valid, but needs to be rehashed with new
parameters
**`SecurePassword.HASH_BYTES`**
Size of the `hash` Buffer returned by `hash` and `hashSync` and used by
`verify` and `verifySync`.
#### Example Usage
```ts
import {AuthenticationError} from "blitz"
import {SecurePassword} from "@blitzjs/auth"
import db from "db"
export const authenticateUser = async (email: string, password: string) => {
const user = await db.user.findFirst({where: {email}})
if (!user) throw new AuthenticationError()
const result = await SecurePassword.verify(user.hashedPassword, password)
if (result === SecurePassword.VALID_NEEDS_REHASH) {
// Upgrade hashed password with a more secure hash
const improvedHash = await SecurePassword.hash(password)
await db.user.update({
where: {id: user.id},
data: {hashedPassword: improvedHash},
})
}
const {hashedPassword, ...rest} = user
return rest
}
```
## `setPublicDataForUser()` {#set-public-data-for-user}
#### `setPublicDataForUser(userId: PublicData['userId'], publicData: Record<any, any>) => void`
This can be used to update the `publicData` of a user's sessions. It can
be useful when changing a user's role, since the new permissions can be
enforced as soon as the user is doing the next request.
#### Example Usage
```ts
import {setPublicDataForUser} from "@blitzjs/auth"
import db from "db"
export const updateUserRole = async (userId: PublicData["userId"], role: string) => {
// update the user's role
await db.user.update({where: {id: userId}, data: {role}})
// update role in all active sessions
await setPublicDataForUser(userId, {role})
}
```

View File

@@ -0,0 +1,17 @@
---
title: Auth Overview
sidebar_label: Overview
---
New Blitz apps have authentication and authorization already set up by
default with user sign-up, log-in, and log-out. Your `db/schema.prisma`
file has a `User` and `Session` model and the auth code is in the
`app/auth/` folder.
Blitz has built-in session management that works with email/password auth
and with any third-party providers.
Blitz session management follows the same approach as the state of the art
[SuperTokens library](https://supertokens.com). The CTO of SuperTokens,
[Rishabh Poddar](https://twitter.com/rishpoddar), designed and oversaw our
implementation. We're extremely grateful for Rishabh's help! 🙏

View File

@@ -0,0 +1,446 @@
---
title: Authorization & Security
sidebar_label: Authorization & Security
---
Authorization is the act of allowing or disallowing access to data and
pages in your application.
## Secure Your Data {#secure-your-data}
You secure data by calling
[`ctx.session.$authorize()`](#ctxsessionauthorize) inside all the queries
and mutations that you want secured. (Or if using
[`resolver.pipe`](./resolver-server-utilities#resolverpipe), by using
[`resolver.authorize`](./resolver-server-utilities#resolverauthorize)).
You can also secure [API routes](./api-routes) the same way.
Those will throw `AuthenticationError` if the user is not logged in, and
it will throw `AuthorizationError` if the user is logged in but doesn't
have the required permissions.
```ts
import {resolver} from "@blitzjs/rpc"
import db from "db"
import * as z from "zod"
const CreateProject = z
.object({
name: z.string(),
})
.nonstrict()
export default resolver.pipe(
resolver.zod(CreateProject),
// highlight-start
resolver.authorize(),
// highlight-end
async (input, ctx) => {
// *: in multi-tenant app, you must add validation to ensure correct tenant
const projects = await db.projects.create({data: input})
return projects
},
)
```
or
```ts
import {Ctx} from "blitz"
import db from "db"
import * as z from "zod"
const CreateProject = z
.object({
name: z.string(),
}).nonstrict()
type CreateProjectType = z.infer<typeof CreateProject>
export default function createProject(input: CreateProjectType, ctx: Ctx) {
const data = CreateProject.parse(input)
// highlight-start
ctx.session.$authorize(),
// highlight-end
// *: in multi-tenant app, you must add validation to ensure correct tenant
const projects = await db.projects.create({data})
return projects
}
```
### Input Validation {#input-validation}
For security, it's very important to validate all input values in your
mutations. We recommended using [zod](https://github.com/colinhacks/zod),
which we include in all our code generation templates. Without this, users
may be able to access data or perform actions that are forbidden.
```ts
import {resolver} from "@blitzjs/rpc"
import db from "db"
import * as z from "zod"
// highlight-start
const CreateProject = z
.object({
name: z.string(),
})
.nonstrict()
// highlight-end
export default resolver.pipe(
// highlight-start
resolver.zod(CreateProject),
// highlight-end
resolver.authorize(),
async (input, ctx) => {
// *: in multi-tenant app, you must add validation to ensure correct tenant
const projects = await db.projects.create({data: input})
return projects
},
)
```
## Secure Your Pages {#secure-your-pages}
Set `Page.authenticate = true` on all pages that require a user to be
logged in. If a user is not logged in, an `AuthenticationError` will be
thrown and caught by your top level Error Boundary.
Or if instead you want to redirect the user, set
`Page.authenticate = {redirectTo: '/login'}`
```tsx
const Page: BlitzPage = () => {
return <div>{/* ... */}</div>
}
// highlight-start
Page.authenticate = true
// or Page.authenticate = {redirectTo: '/login'}
// or Page.authenticate = {redirectTo: Routes.Login()}
// highlight-end
export default Page
```
### Redirecting Logged In Users {#redirecting-logged-in-users}
For pages that are only for logged out users, such as login and signup
pages, set `Page.redirectAuthenticatedTo = '/'` to automatically redirect
logged in users to another page.
```tsx
import {Routes} from "@blitzjs/next"
const Page: BlitzPage = () => {
return <div>{/* ... */}</div>
}
// highlight-start
// using full path
Page.redirectAuthenticatedTo = "/"
// using route manifest
Page.redirectAuthenticatedTo = Routes.Home()
// using function
Page.redirectAuthenticatedTo = ({session}) => (session.role === "admin" ? "/admin" : Routes.Home())
// highlight-end
export default Page
```
### Secure Layouts {#secure-layouts}
You can secure layouts as you secure pages:
```tsx
const Layout = ({children}) => {
return <div>{children}</div>
}
// highlight-start
Layout.authenticate = true
// or Layout.authenticate = {redirectTo: '/login'}
// or Layout.authenticate = {redirectTo: Routes.Login()}
// or Layout.redirectAuthenticatedTo = Routes.Home()
// highlight-end
const Page = () => {
return <div>{/* ... */}</div>
}
Page.getLayout = (page) => <Layout>{page}</Layout>
export default Page
```
Blitz will take the first component it finds from the response of
`getLayout` with either a `authenticate` key or a
`redirectAuthenticatedTo` key and uses its values for authentication.
These values can be overwritten in a per-page basis with
`Page.authenticate` or `Page.redirectAuthenticatedTo`:
```tsx
const Layout: BlitzLayout = ({children}) => {
return <div>{children}</div>
}
Layout.authenticate = true
const Page: BlitzPage = () => {
return <div>{/* ... */}</div>
}
Page.getLayout = (page) => <Layout>{page}</Layout>
// highlight-start
Page.authenticate = false
// highlight-end
export default Page
```
## UX for Unauthorized Users {#ux-for-unauthorized-users}
While you can use redirects to and from a `/login` page, we recommended to
use Error Boundaries instead of redirects.
In React, the way you catch errors in your UI is to use an
[error boundary](https://reactjs.org/docs/error-boundaries.html).
You should have a top level error boundary inside `_app.tsx` so that these
errors are handled from everywhere in our app. And then if you need, you
can also place more error boundaries at other places in your app.
The default error handling setup in new Blitz apps is as follows:
- If `AuthenticationError` is thrown, directly show the user a login form
instead of redirecting to a separate route. This prevents the need to
manage redirect URLs. Once the user logs in, the error boundary will
reset and the user can access the original page they wanted to access.
- If `AuthorizationError` is thrown, display an error stating such.
And here's the default `RootErrorFallback` that's in `app/pages/_app.tsx`.
You can customize it as required for your needs.
```tsx
import {AuthenticationError, AuthorizationError} from "blitz"
import {ErrorComponent, ErrorFallbackProps} from "@blitzjs/next"
function RootErrorFallback({error, resetErrorBoundary}: ErrorFallbackProps) {
if (error instanceof AuthenticationError) {
return <LoginForm onSuccess={resetErrorBoundary} />
} else if (error instanceof AuthorizationError) {
return (
<ErrorComponent
statusCode={error.statusCode}
title="Sorry, you are not authorized to access this"
/>
)
} else {
return (
<ErrorComponent statusCode={error.statusCode || 400} title={error.message || error.name} />
)
}
}
```
For more information on error handling in Blitz, see the
[Error Handling documentation](./error-handling).
## Displaying Different Content Based on User Role {#displaying-different-content-based-on-user-role}
There's two approaches you can use to check the user role in your UI.
#### `useSession()`
The first way is to use the
[`useSession()`](./session-management#session-on-the-client) hook to read
the user role from the session's `publicData`.
This is available on the client without making a network call to the
backend, so it's available faster than the `useCurrentUser()` approach
described below.
Note: due to the nature of static pre-rendering, the `session` will not
exist on the very first render on the client. This causes a quick "flash"
on first load. You can fix that by setting
[`Page.suppressFirstRenderFlicker = true`](./pages##automatic-static-optimization)
```tsx
import {useSession} from "@blitzjs/auth"
const session = useSession()
if (session.role === "admin") {
return /* admin stuff */
} else {
return /* normal stuff */
}
```
#### `useCurrentUser()`
The second way is to use the `useCurrentUser()` hook. New Blitz apps by
default have a `useCurrentUser()` hook and a corresponding
`getCurrentUser` query. Unlike the `useSession()` approach above,
`useCurrentUser()` will require a network call and thus be slower.
However, if you need access to user data that would be insecure to store
in the session's `publicData`, you would need to use `useCurrentUser()`
instead of `useSession()`.
```tsx
import {useCurrentUser} from "app/users/hooks/useCurrentUser"
const user = useCurrentUser()
if (user.isFunny) {
return /* funny stuff */
} else {
return /* normal stuff */
}
```
## `isAuthorized` Adapters {#is-authorized-adapters}
The implementation of `ctx.session.$isAuthorized()` and
`ctx.session.$authorize()` are defined by an adapter which you set in the
`sessionMiddleware()` config.
##### `ctx.session.$isAuthorized()`
Always returns a boolean indicating if user is authorized
##### `ctx.session.$authorize()`
**Throws an error** if the user is not authorized. This is what you most
commonly use to secure your queries and mutations.
```ts
import {Ctx} from "blitz"
import {GetUserInput} from "./somewhere"
export default async function getUser({where}: GetUserInput, ctx: Ctx) {
// highlight-start
ctx.session.$authorize("admin")
// highlight-end
return await db.user.findOne({where})
}
```
#### `simpleRolesIsAuthorized` (default in new apps)
##### Setup
To use, add it to your `sessionMiddleware` configuration (this is already
set up by default in new apps).
```js
// blitz.config.js
const {sessionMiddleware, simpleRolesIsAuthorized} = require("blitz")
module.exports = {
middleware: [
sessionMiddleware({
// highlight-start
isAuthorized: simpleRolesIsAuthorized,
// highlight-end
}),
],
}
```
And if using TypeScript, set the type in `types.ts` like this:
```ts
import {SimpleRolesIsAuthorized} from "@blitzjs/auth"
type Role = "ADMIN" | "USER"
declare module "@blitzjs/auth" {
// highlight-start
export interface Session {
isAuthorized: SimpleRolesIsAuthorized<Role>
}
// highlight-end
}
```
##### `ctx.session.$isAuthorized(roleOrRoles?: string | string[])`
Example usage:
```ts
// User not logged in
ctx.session.$isAuthorized() // false
// User logged in with 'customer' role
ctx.session.$isAuthorized() // true
ctx.session.$isAuthorized("customer") // true
ctx.session.$isAuthorized("admin") // false
```
##### `ctx.session.$authorize(roleOrRoles?: string | string[])`
Example usage:
```ts
// User not logged in
ctx.session.$authorize() // throws AuthenticationError
// User logged in with 'customer' role
ctx.session.$authorize() // success - no error
ctx.session.$authorize("customer") // success - no error
ctx.session.$authorize("admin") // throws AuthorizationError
ctx.session.$authorize(["admin", "customer"]) // success - no error
```
### Making a Custom Adapter {#making-a-custom-adapter}
An `isAuthorized` adapter must conform to the following function
signature.
```ts
type CustomIsAuthorizedArgs = {
ctx: any
args: [/* args that you want for session.$authorize(...args) */]
}
export function customIsAuthorized({ctx, args}: CustomIsAuthorizedArgs) {
// can access ctx.session, ctx.session.userId, etc
}
```
##### Example
Here's the source code for the `simpleRolesIsAuthorized` adapter include
in Blitz core as of Jan 26, 2021.
```ts
type SimpleRolesIsAuthorizedArgs = {
ctx: any
args: [roleOrRoles?: string | string[]]
}
export function simpleRolesIsAuthorized({ctx, args}: SimpleRolesIsAuthorizedArgs) {
const [roleOrRoles, options = {}] = args
const condition = options.if ?? true
// No roles required, so all roles allowed
if (!roleOrRoles) return true
// Don't enforce the roles if condition is false
if (!condition) return true
const rolesToAuthorize = []
if (Array.isArray(roleOrRoles)) {
rolesToAuthorize.push(...roleOrRoles)
} else if (roleOrRoles) {
rolesToAuthorize.push(roleOrRoles)
}
for (const role of rolesToAuthorize) {
if ((ctx.session as SessionContext).$publicData.roles!.includes(role)) return true
}
return false
}
```

View File

@@ -0,0 +1,85 @@
---
title: Custom Babel Config
sidebar_label: Babel Config
---
[Babel](https://babeljs.io/) is a toolchain that is mainly used to convert
ECMAScript 2015+ code into a backwards compatible version of JavaScript in
current and older browsers or environments. Here are the main things Babel
can do for you:
- Transform syntax
- Polyfill features that are missing in your target environment (through a
third-party polyfill such as
[core-js](https://github.com/zloirock/core-js))
- Source code transformations (codemods)
## Configuration File {#config}
Blitz adds the `blitz/babel` preset to new apps via the `babel.config.js`
file in the app root. This preset contains everything needed to compile
React applications and server-side code, including source code
transformations that enable
[the import and use of server code](https://blitzjs.com/docs/client-and-server)
directly in your UI components.
If you want to extend the default Babel configs, this is possible. The
`babel.config.js` at the top of your app is considered the source of
truth, and therefore it needs to define what Blitz needs as well, which is
the `blitz/babel` preset.
Here's the default example `blitz.config.js` file:
```jsx
module.exports = {
presets: ["blitz/babel"],
plugins: [],
}
```
You can
[take a look at this file](https://github.com/blitz-js/blitz/blob/canary/packages/babel-preset/src/index.ts)
to learn about the presets included by `blitz/babel`.
To add presets/plugins without configuring them, you can do it this way:
```jsx
module.exports = {
presets: ["blitz/babel"],
plugins: ["@babel/plugin-proposal-do-expressions"],
}
```
To add presets/plugins with custom configuration, do it on the
`blitz/babel` preset like so:
```jsx
module.exports = {
presets: [
"blitz/babel",
{
"preset-env": {},
"transform-runtime": {},
"styled-jsx": {},
"class-properties": {},
},
],
plugins: [],
}
```
<Card type="info">
To learn more about the available options for each config, visit their
documentation site.
Blitz uses the current Node.js version for server-side compilations.
</Card>
<Card type="caution">
The modules option on "preset-env" should be kept to false, otherwise
webpack code splitting is turned off.
</Card>

View File

@@ -0,0 +1,521 @@
---
title: blitz.config.js
sidebar_label: blitz.config.js
---
You can customize advanced behavior of Blitz with `blitz.config.js` or
`blitz.config.ts`.
Take a look at the following `blitz.config.js` example:
```js
module.exports = {
/* config options here */
}
```
You can also use a function:
```js
module.exports = (phase, {defaultConfig}) => {
return {
/* config options here */
}
}
```
`phase` is the current context in which the configuration is loaded. You
can see the available phases
[here](https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/constants.ts#L1-L4).
Phases can be imported from `next/constants`:
```js
import {PHASE_DEVELOPMENT_SERVER} from "next/constants"
module.exports = (phase, {defaultConfig}) => {
if (phase === PHASE_DEVELOPMENT_SERVER) {
return {
/* development only config options here */
}
}
return {
/* config options for all phases except development here */
}
}
```
The commented lines are the place where you can put the configs allowed by
`blitz.config.js`, which are defined
[here](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config.ts#L12-L63).
However, none of the configs are required, and it's not necessary to
understand what each config does, instead, search for the features you
need to enable or modify in this section and they will show you what to
do.
## Webpack Config {#webpack-config}
You can customize the Blitz webpack config. See
[our webpack documentation](./webpack-config) for details
## Build Target {#build-target}
Blitz supports various build targets, each changing the way your
application is built and run. We'll explain each of the targets below.
#### `server` target
Your application will be built and deployed as a monolith. This is the
default target and no action is required on your part to opt-in.
#### `serverless` target
This target will output independent pages that don't require a monolithic
server.
It's only compatible with Serverless deployment platforms (like
[Vercel](https://vercel.com)) — you cannot use the custom server API.
To opt-into this target, set the following configuration in your
`blitz.config.js`:
```js
module.exports = {
target: "serverless",
}
```
## Middleware {#middleware}
HTTP middleware can be added queries and mutations.
[See the middleware documentation](./middleware) for full details.
```js
module.exports = {
middleware: [
(req, res, next) => {
res.blitzCtx.referer = req.headers.referer
return next()
},
],
```
## Logging {#logging}
### Log Level {#log-level}
By default Blitz logs various things at runtime. You can adjust the
verbosity using the log level setting.
- Default: `info`
- Options: `trace` | `debug` | `info` | `warn` | `error` | `fatal`
```js
module.exports = {
log: {
level: "info",
},
}
```
### Log Type {#log-type}
It is also possible to adjust the logging type, which could be helpful if
you prefer to forward the logs in JSON format.
- Default: `pretty`
- Options: `pretty` | `json` | `hidden`
```js
module.exports = {
log: {
type: "pretty",
},
}
```
## Codegen {#codegen}
If you want to use custom templates with `blitz generate` instead of the
default ones (e.g. with different styles), you can provide a path to the
local template directory:
```js
module.exports = {
codegen: {
templateDir: "./templates",
},
}
```
The template directory should have the following structure:
```
.
├── form
│ └── __ModelName__Form.tsx
├── mutation
│ └── __input__.ts
├── mutations
│ ├── create__ModelName__.ts
│ ├── delete__ModelName__.ts
│ └── update__ModelName__.ts
├── page
│ ├── __modelIdParam__
│ │ └── edit.tsx
│ ├── __modelIdParam__.tsx
│ ├── index.tsx
│ └── new.tsx
├── queries
│ ├── get__ModelName__.ts
│ └── get__ModelNames__.ts
└── query
└── __name__.ts
```
If some folders are omitted in your custom template directory,
`blitz generate` will fallback to the default templates. You can check
them out
[here](https://github.com/blitz-js/blitz/tree/canary/packages/generator/templates).
## CLI {#cli}
### Clear Console On Blitz Dev {#clear-console-on-blitz-dev}
When you run `blitz dev`, the console gets cleared. You can disable it by
setting the `cli.clearConsoleOnBlitzDev` to `false`:
```js
module.exports = {
cli: {
clearConsoleOnBlitzDev: false,
},
}
```
### Proxy support {#proxy-support}
Proxy support is enabled automatically for recipe install if either
`http_proxy` or `https_proxy` environment variable is present. You can
also set proxy using:
```js
module.exports = {
cli: {
httpProxy: "http://127.0.0.1:8080",
httpsProxy: "http://127.0.0.1:8080",
noProxy: "localhost",
},
}
```
Please note that proxy configuration in `blitz.config.js` will override
environment proxy configuration.
## CDN Support with Asset Prefix {#cdn-support-with-asset-prefix}
To set up a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network),
you can set up an asset prefix and configure your CDN's origin to resolve
to the domain that Blitz is hosted on.
```js
const isProd = process.env.NODE_ENV === "production"
module.exports = {
// Use the CDN in production and localhost for development.
assetPrefix: isProd ? "https://cdn.mydomain.com" : "",
}
```
Blitz.js will automatically use your asset prefix for the JavaScript and
CSS files it loads from the `/_next/` path (`.next/static/` folder). For
example, with the above configuration, the following request for a JS
chunk:
```
/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js
```
Would instead become:
```
https://cdn.mydomain.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js
```
The exact configuration for uploading your files to a given CDN will
depend on your CDN of choice. The only folder you need to host on your CDN
is the contents of `.next/static/`, which should be uploaded as
`_next/static/` as the above URL request indicates. **Do not upload the
rest of your `.next/` folder**, as you should not expose your server code
and other configuration to the public.
While `assetPrefix` covers requests to `_next/static`, it does not
influence the following paths:
- Files in the [public](./static-files) folder; if you want to serve those
assets over a CDN, you'll have to introduce the prefix yourself
- `/_next/data/` requests for `getServerSideProps` pages. These requests
will always be made against the main domain since they're not static.
- `/_next/data/` requests for `getStaticProps` pages. These requests will
always be made against the main domain to support
[Incremental Static Generation](./get-static-props), even if you're not
using it (for consistency).
## Custom Page Extensions {#custom-page-extensions}
Aimed at modules like
[@next/mdx](https://github.com/vercel/next.js/tree/canary/packages/next-mdx),
which adds support for pages ending with `.mdx`. You can configure the
extensions looked for in the `pages` directory when resolving pages.
```js
module.exports = {
pageExtensions: ["mdx", "jsx", "js", "ts", "tsx"],
}
```
## Static Optimization Indicator {#static-optimization-indicator}
When a page qualifies for
[Automatic Static Optimization](./pages#automatic-static-optimization) we
show an indicator to let you know.
This is helpful since automatic static optimization can be very beneficial
and knowing immediately in development if the page qualifies can be
useful.
In some cases this indicator might not be useful, like when working on
electron applications.
```js
module.exports = {
devIndicators: {
autoPrerender: false,
},
}
```
## Configuring onDemandEntries for Development {#configuring-on-demand-entries-for-development}
Blitz exposes some options that give you some control over how the server
will dispose or keep in memory built pages in development.
```js
module.exports = {
onDemandEntries: {
// period (in ms) where the server will keep pages in the buffer
maxInactiveAge: 25 * 1000,
// number of pages that should be kept simultaneously without being disposed
pagesBufferLength: 2,
},
}
```
## Setting a custom build directory {#setting-a-custom-build-directory}
You can specify a name to use for a custom build directory to use instead
of `.next`.
```js
module.exports = {
distDir: "build",
}
```
Now if you run `blitz build` Blitz will use `build` instead of the default
`.next` folder.
> `distDir` **should not** leave your project directory. For example,
> `../build` is an **invalid** directory.
## Configuring the Build ID {#configuring-the-build-id}
Blitz uses a constant id generated at build time to identify which version
of your application is being served. This can cause problems in
multi-server deployments when `blitz build` is ran on every server. In
order to keep a static build id between builds you can provide your own
build id.
```js
module.exports = {
generateBuildId: async () => {
// You can, for example, get the latest git commit hash here
return "my-build-id"
},
}
```
## Compression {#compression}
Blitz provides [**gzip**](https://tools.ietf.org/html/rfc6713#section-3)
compression to compress rendered content and static files. Compression
only works with the [`server` target](#server-target). In general you will
want to enable compression on a HTTP proxy like
[nginx](https://www.nginx.com/), to offload load from the `Node.js`
process.
```js
module.exports = {
compress: false,
}
```
## Disabling ETag Generation {#disabling-e-tag-generation}
By default we generate [etags](https://en.wikipedia.org/wiki/HTTP_ETag)
for every page by default. You may want to disable etag generation for
HTML pages depending on your cache strategy.
```js
module.exports = {
generateEtags: false,
}
```
## Disabling x-powered-by {#disabling-x-powered-by}
By default Blitz adds the `x-powered-by` header. To opt-out of it, disable
the `poweredByHeader` config:
```js
module.exports = {
poweredByHeader: false,
}
```
## Base Path {#base-path}
To deploy a Blitz application under a sub-path of a domain you can use the
`basePath` config option.
`basePath` allows you to set a path prefix for the application. For
example, to use `/docs` instead of `/` (the default), open
`blitz.config.js` and add the `basePath` config:
```js
module.exports = {
basePath: "/docs",
}
```
Note: this value must be set at build time and can not be changed without
re-building as the value is inlined in the client-side bundles.
### Links {#links}
When linking to other pages using `Link` and `Router` the `basePath` will
be automatically applied.
For example using `/about` will automatically become `/docs/about` when
`basePath` is set to `/docs`.
```js
export default function HomePage() {
return (
<>
<Link href={Routes.About()}>
<a>About Page</a>
</Link>
</>
)
}
```
Output html:
```html
<a href="/docs/about">About Page</a>
```
This makes sure that you don't have to change all links in your
application when changing the `basePath` value.
## Trailing Slash {#trailing-slash}
By default Blitz will redirect urls with trailing slashes to their
counterpart without a trailing slash. For example `/about/` will redirect
to `/about`. You can configure this behavior to act the opposite way,
where urls without trailing slashes are redirected to their counterparts
with trailing slashes.
Open `blitz.config.js` and add the `trailingSlash` config:
```js
module.exports = {
trailingSlash: true,
}
```
With this option set, urls like `/about` will redirect to `/about/`.
## Custom Server {#custom-server}
You can setup and configure some items in the blitz config regarding
custom servers. [See the custom server documentation](./custom-server) for
full details.
## Experimental {#experimental}
These features aren't ready for production and could change at any time.
### Isomorphic Resolver Imports {#isomorphic-resolver-imports}
This enables isomorphic code imports from resolver files (for example
exporting a zod schema from a mutation and being able to import that in
client code).
- Default: `false`
```js
module.exports = {
experimental: {
isomorphicResolverImports: true,
},
}
```
### React Root Mode {#react-mode}
By default Blitz uses
[React Concurrent Mode](https://reactjs.org/docs/concurrent-mode-intro.html).
You can disable it by changing `experimental.reactRoot` to `false`.
- Default: `true`
```js
module.exports = {
experimental: {
reactRoot: false,
},
}
```
### Disabling HTTP Keep-Alive {#disable-keep-alive}
Blitz.js automatically polyfills
[node-fetch](/docs/basic-features/supported-browsers-features#polyfills)
and enables
[HTTP Keep-Alive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive)
by default. You may want to disable HTTP Keep-Alive for certain `fetch()`
calls or globally.
For a single `fetch()` call, you can add the agent option:
```js
import {Agent} from "https"
const url = "https://example.com"
const agent = new Agent({keepAlive: false})
fetch(url, {agent})
```
To override all `fetch()` calls globally, you can use `blitz.config.js`:
```js
module.exports = {
httpAgentOptions: {
keepAlive: false,
},
}
```

View File

@@ -0,0 +1,69 @@
---
title: Future of Blitz
sidebar_label: ⚠️ Future of Blitz
---
**Blitz will be pivoting to a framework agnostic toolkit that preserves
the world-class DX and features we adore but brings it to all Next.js
users and optionally Remix, SvelteKit, Nuxt, etc.**
Your existing Blitz apps will continue to run, and we will fix any
critical bugs that come up. The current Blitz codebase will remain. It may
move to a different repo, but it will be preserved and we can continue to
make releases as needed. We'll strive to make the migration to the Blitz
toolkit as seamless as possible.
## Frequently Asked Questions {#faq}
#### 1. Will this migration force me to do a lot of work?
Nope - we are working to make the new setup as close as possible to the
current setup. For any changes required, we will have a codemod that will
automate all, or almost all, of any changes required. The effort on your
part should be minimal.
#### 2. I'm looking to start a new app, is it safe to start with Blitz?
Yes, for sure you can start new Blitz apps. Nothing has changed with the
current framework, and the future migration will be seamless. The
productivity boost from Blitz will put you way ahead of manually sourcing
your own Next.js stack from scratch, even with the coming migration.
#### 3. My app is almost ready for production, do I need to rewrite it?
No! Go ahead and ship your Blitz app to production with confidence. We'll
continue to fix any significant bugs.
## Pivot Objectives {#objectives}
- Preserve the DX and features we currently have
- Make it effortless for millions more of developers to benefit from Blitz
- Decouple Blitz from any specific framework
- Enable us to ship features significant faster without the burden
maintaining the Next.js fork
Blitz toolkit will be meant for building anything with a database, from
side projects to massive enterprise apps. It will both enhance fullstack
DX and provide the missing backend components for JS that Rails and
Laravel enjoy.
## Features {#features}
We believe the core value that Blitz currently provides is:
1. The zero-api data layer
2. Authentication
3. Authorization
4. Conventions
5. New app templates & code scaffolding
A new toolkit would start with these as the foundation. We think almost
everything Blitz currently adds would be migrated to the toolkit including
recipes, typesafe Routes manifest, etc. Then wed start shipping a ton
more awesome things.
## Further reading {#further-reading}
[For more details check out the Blitz Pivot RFC](https://github.com/blitz-js/blitz/discussions/3075).
It goes into detail on why this is happening. It includes an outline of
the toolkit, and a list of the features wed like to add.

View File

@@ -0,0 +1,110 @@
---
title: Add Blitz.js to an Existing Next.js Project
sidebar_label: With Existing Next.js App
---
If you have an existing Next.js project and would like to use some or all
of Blitz Toolkit, this page will provide you with information on setting
it up. A few steps are required, and we'll go through them one by one.
## Adding required dependencies {#adding-dependencies}
```sh
yarn add blitz @blitzjs/next
# or
pnpm add blitz @blitzjs/next
# or
npm i blitz @blitzjs/next
```
## Blitz server setup {#blitz-server-setup}
If you want to use Blitz's server functionalities like auth, middlewares,
rpc, you'd need to create a `blitz-server.ts` file somewhere in your
project, e.g. in `app/blitz-server.ts`. We'll cover how to add plugins
later.
```ts
import {setupBlitzServer} from "@blitzjs/next"
const {
/* plugins' exports */
} = setupBlitzServer({
plugins: [
// plugins will go here
],
})
```
## Blitz client setup {#blitz-client-setup}
Now, if you want Blitz's client functionalities, you'll have to create a
`blitz-client.ts` file. It can be next to the `blitz-server.ts` in
`app/blitz-client.ts`.
```ts
import {setupBlitzClient} from "@blitzjs/next"
export const {withBlitz} = setupBlitzClient({
plugins: [
// plugins will go here
],
})
```
The `withBlitz` function will be needed to wrap your components with
Blitz's client side functionality.
## Use `withBlitz` in your App component {#use-withblitz-in-app-component}
To use Blitz on the client, you also have to use the `withBlitz` function
in your App component.
```ts
import {withBlitz} from "app/blitz-client"
function App({Component, pageProps}: AppProps) {
return <Component {...pageProps} />
}
export default withBlitz(App)
```
## Modifying `next.config.js` file {#modifying-next-config-file}
Next.js requires you to manually type out page locations. Blitz comes with
a [Route Manifest](./route-manifest), so you can do:
```tsx
<Link href={Routes.ProductsPage({ productId: 123 })} />
// instead of
<Link href={`/products/${123}`} />
```
To enable it, you have to wrap your config with `withBlitz` in the
`next.config.js` file:
```js
const {withBlitz} = require("@blitzjs/next")
module.exports = withBlitz()
```
## Adding plugins {#adding-plugins}
Now that you're all set with the basic setup, you can add plugins that you
want to use in your app. There are a few places to check out:
1. [`@blitzjs/auth`](./auth-setup) — it covers how to setup auth plugin as
well as how to use Blitz auth system.
2. [`@blitzjs/rpc`](./rpc-setup) — check it out to learn how to set up
Blitz's Zero API Layer and to learn more about it.
Finally, you can check out more detailed information about the
[`@blitzjs/next` adapter](./blitzjs-next) to learn how to use Blitz
functionalities inside of `getServerSideProps`, `getStaticProps`, Next API
Routes, and other places.

View File

@@ -0,0 +1,179 @@
---
title: "@blitzjs/next"
sidebar_label: "@blitzjs/next"
---
## Overview {#blitzjs-next-overview}
The `@blitzjs/next` adapter exposes functions & components specific for
the Next.js framework.
## Setup {#setup-blitzjs-next}
You can install `@blitzjs/next` by running the following command:
```bash
npm i @blitzjs/next # yarn add @blitzjs/next # pnpm add @blitzjs/next
```
## Client {#blitzjs-next-client}
### Example {#blitzjs-next-client-example}
Inside `app/blitz-client.ts`:
```ts
import {setupBlitzClient} from "@blitzjs/next"
export const {withBlitz} = setupBlitzClient({
plugins: [],
})
```
Then inside `pages > _app.tsx` wrap `MyApp` function with the `withBlitz`
HOC component.
```ts
import {ErrorFallbackProps, ErrorComponent, ErrorBoundary} from "@blitzjs/next"
import {AuthenticationError, AuthorizationError} from "blitz"
import type {AppProps} from "next/app"
import React, {Suspense} from "react"
import {withBlitz} from "app/blitz-client"
function RootErrorFallback({error}: ErrorFallbackProps) {
if (error instanceof AuthenticationError) {
return <div>Error: You are not authenticated</div>
} else if (error instanceof AuthorizationError) {
return (
<ErrorComponent
statusCode={error.statusCode}
title="Sorry, you are not authorized to access this"
/>
)
} else {
return (
<ErrorComponent
statusCode={(error as any)?.statusCode || 400}
title={error.message || error.name}
/>
)
}
}
function MyApp({Component, pageProps}: AppProps) {
return (
<ErrorBoundary FallbackComponent={RootErrorFallback}>
<Component {...pageProps} />
</ErrorBoundary>
)
}
export default withBlitz(MyApp)
```
<Card type="note">
An `<ErrorBoundary />` provider and `<ErrorComponent />` component is supplied by `@blitzjs/next`
</Card>
### API {#blitzjs-next-client-api}
```ts
setupBlitzClient({
plugins: [],
})
```
#### Arguments
- `plugins:` An array of blitzjs plugins
- **Required**
#### Returns
An object with the `withBlitz` HOC wrapper
## Server {#blitzjs-next-server}
### Example {#blitzjs-next-server-example}
Inside `app > blitz-server.ts`
```ts
import {setupBlitzServer} from "@blitzjs/next"
export const {gSSP, gSP, api} = setupBlitzServer({
plugins: [],
})
```
<Card type="note">
The `gSSP`, `gSP` & `api` functions all pass down the context of the session if you're using the
auth plugin. Read more about the auth plugin here [@blitzjs/auth](/docs/auth).
</Card>
### API {#blitzjs-next-server-api}
```ts
setupBlitzServer({
plugins: [],
onError?: (err) => void
})
```
#### Arguments
- `plugins:` An array of blitzjs plugins
- **Required**
- `onError:` Catch all errors _(Great for services like sentry)_
#### Returns
An object with the [`gSSP`](#blitzjs-next-gssp), [`gSP`](#blitzjs-next-gsp) & [`api`](#blitzjs-next-api) wrappers.
## gSP {#blitzjs-next-gsp}
```ts
import {gSP} from "app/blitz-server"
export const getStaticProps = gSP(async ({ctx}) => {
return {
props: {
data: {
userId: ctx?.session.userId,
session: {
id: ctx?.session.userId,
publicData: ctx?.session.$publicData,
},
},
},
}
})
```
## gSSP {#blitzjs-next-gssp}
```ts
import {gSSP} from "app/blitz-server"
export const getServerSideProps = gSSP(async ({ctx}) => {
return {
props: {
userId: ctx?.session.userId,
publicData: ctx?.session.$publicData,
},
}
})
```
## api {#blitzjs-next-api}
```ts
import {api} from "app/blitz-server"
export default api(async (req, res, ctx) => {
res.status(200).json({userId: ctx?.session.userId})
})
```
_For more information about Next.js API routes, visit their docs at
[https://nextjs.org/docs/api-routes/introduction](https://nextjs.org/docs/api-routes/introduction)_

View File

@@ -0,0 +1,38 @@
---
title: blitz autocomplete
sidebar_label: blitz autocomplete
---
Displays autocomplete installation instructions.
> **Note**: Autocomplete currently
> [doesn't work on Windows](https://github.com/oclif/plugin-autocomplete/issues/2)
```bash
blitz autocomplete [shell]
```
| Argument | Required | Description |
| -------- | -------- | --------------- |
| `shell` | No | `zsh` or `bash` |
#### Example Output
```bash
> blitz autocomplete zsh
Building the autocomplete cache... done
Setup Instructions for BLITZ CLI Autocomplete ---
1) Add the autocomplete env var to your zsh profile and source it
$ printf "$(blitz autocomplete:script zsh)" >> ~/.zshrc; source ~/.zshrc
NOTE: After sourcing, you can run `$ compaudit -D` to ensure no permissions conflicts are present
2) Test it out, e.g.:
$ blitz <TAB> # Command completion
$ blitz command --<TAB> # Flag completion
Enjoy!
```

View File

@@ -0,0 +1,36 @@
---
title: blitz build
sidebar_label: blitz build
---
**Alias: `blitz b`**
Create a production build of your Blitz app. It compiles your Blitz app
into Next.js runtime code inside `.next`
This is a super thin wrapper over Next CLI to improve environment variable
loading.
```bash
blitz dev -e staging
```
It does the following:
1. Loads correct `.env.X` and `.env.X.local` files based on the `-e X`
flag that you specify. Ex: `-e staging` loads `.env.staging`.
2. Sets `process.env.APP_ENV` to the env name. Ex: `-e staging` =>
`APP_ENV=staging`.
3. Passes all other arguments directly to the next build command
#### Options
| Option | Shorthand | Description | Default |
| ------- | --------- | ------------------------------------------------------------------------------------- | ------- |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Examples
```bash
blitz build
```

View File

@@ -0,0 +1,23 @@
---
title: blitz codegen
sidebar_label: blitz codegen
---
**Alias: `blitz cg`**
Use this command to generate the [Route Manifest](./route-manifest) and
Prisma client (if Prisma schema is defined in `package.json`).
#### Options
| Option | Shorthand | Description | Default |
| ------- | --------- | ------------------------------------------------------------------------------------- | ------- |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Example
```
> blitz codegen
✔ Compiled
```

View File

@@ -0,0 +1,42 @@
---
title: blitz console
sidebar_label: blitz console
---
**Alias: `blitz c`**
Starts a Node.js REPL with your [database](./database-overview) and all
your [queries](./query-resolvers) and [mutations](./mutation-resolvers)
preloaded.
This makes it very simple to read or write to your database and run
one-off commands using your queries or mutations. It's also great for
testing and rapid prototype exploration.
#### Options
| Option | Shorthand | Description | Default |
| ------- | --------- | ------------------------------------------------------------------------------------- | ------- |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Examples
```bash
> blitz console
You have entered the Blitz console
Tips: - Exit by typing .exit or pressing Ctrl-D
- Use your db like this: await db.project.findMany()
- Use your queries/mutations like this: await getProjects()
✔ Loading your code
⚡ > await db.question.findMany()
[
{
id: 1,
createdAt: 2020-06-15T15:06:14.959Z,
updatedAt: 2020-06-15T15:06:14.959Z,
text: "What's up?"
}
]
```

View File

@@ -0,0 +1,32 @@
---
title: blitz db
sidebar_label: blitz db
---
<Card type="caution">
This command is now deprecated in favor of using the `blitz prisma`
command. The `blitz db seed` command still works until Prisma finishes
adding their native seeding functionatily.
</Card>
### `blitz db seed` {#blitz-db-seed}
This will run a script provided with the optional `file` flag. If omitted,
it defaults to `db/seeds`. This is often used to quickly set up the
development environment. For examples check [seeds docs](database-seeds)
#### Options
| Argument | Short | Description | Default |
| -------- | ----- | ---------------------------------------------------------------------------------------------------------------------------- | -------- |
| `--file` | `-f` | Path to the seeds file, relative to the project root folder. Examples: db/seeds, db/seeds.ts, db/seeds/index.ts, db/my-seeds | db/seeds |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Examples
```bash
blitz db seed
blitz db seed -f=db/path-to-seeds-script
```

View File

@@ -0,0 +1,40 @@
---
title: blitz dev
sidebar_label: blitz dev
---
**Alias: `blitz d`**
Starts the Blitz development server.
This is a super thin wrapper over Next CLI to improve environment variable
loading.
```bash
blitz dev -e staging
```
It does the following:
1. Loads correct `.env.X` and `.env.X.local` files based on the `-e X`
flag that you specify. Ex: `-e staging` loads `.env.staging`.
2. Sets `process.env.APP_ENV` to the env name. Ex: `-e staging` =>
`APP_ENV=staging`.
3. Passes all other arguments directly to the next build command
#### Options
| Option | Shorthand | Description | Default |
| ------------------------ | --------- | ------------------------------------------------------------------------------------- | ------------- |
| `--hostname` | `-H` | Set the hostname to use for the server. | `"localhost"` |
| `--port` | `-p` | Set the port you'd like the server to listen on. | `3000` |
| `--no-incremental-build` | | Disable incremental build and start from a fresh cache | `false` |
| `--inspect` | | Enable the Node.js inspector | `false` |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Examples
```bash
blitz dev
blitz dev -p 4000
```

View File

@@ -0,0 +1,23 @@
---
title: blitz export
sidebar_label: blitz export
---
**Alias: `blitz e`**
Exports your Blitz app as a [static application](static-html-export). Make
sure to run `blitz build` before!
#### Options
| Option | Description | Default |
| --------------- | ------------------------------------------------------------------------------------- | ------- |
| `-o` | Set the output directory. | `out/` |
| `--env` or `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Examples
```bash
blitz build
blitz export
```

View File

@@ -0,0 +1,278 @@
---
title: blitz generate
sidebar_label: blitz generate
---
**Alias: `blitz g`**
Use this command to scaffold all the boring code into your project.
Can generate pages, queries, mutations, and Prisma models. Support for
custom templates based on the built-in templates is coming soon, so you
can customize the generator to your app's needs.
```bash
blitz generate [type] [model]
```
| Argument | Required | Description |
| -------- | -------- | ---------------------------------------------------- |
| `type` | Yes | Type of files to generate. Options are listed below. |
| `model` | Yes | The model name to generate files for \* |
> \* `model` can't be any of the following reserved words or their
> plurals: `page`, `api`, `query` or `mutation`.
Here's the matrix of which files are generated by which command:
| Type | Model | Queries | Mutations | Pages |
| ----------- | ----- | ------- | --------- | ----- |
| `all` | Yes | Yes | Yes | Yes |
| `resource` | Yes | Yes | Yes | |
| `model` | Yes | | | |
| `crud` | | Yes | Yes | |
| `queries` | | Yes | | |
| `query` | | Yes | | |
| `mutations` | | | Yes | |
| `mutation` | | | Yes | |
| `pages` | | | | Yes |
##### Example Output
`blitz generate all project` will generate the following files:
```
app/pages/projects/[projectId]/edit.tsx
app/pages/projects/[projectId].tsx
app/pages/projects/index.tsx
app/pages/projects/new.tsx
app/projects/components/ProjectForm.tsx
app/projects/queries/getProject.ts
app/projects/queries/getProjects.ts
app/projects/mutations/createProject.ts
app/projects/mutations/deleteProject.ts
app/projects/mutations/updateProject.ts
```
For the above example, you can view the generated project index page at
[localhost:3000/projects](http://localhost:3000/projects)
## Options {#options}
##### `context/model`
For organization of files within your project, you can specify a nested
folder path in which to generate the files.
```bash
blitz generate all admin/products
// Will generate files in `app/admin/products` instead of `app/products`
```
Alternatively, you can provide the folder path via the `--context` or `-c`
options
##### `--parent`
Shorthand: `-p`
Used to specify that you want to generate files for a model which is a
child of a parent model.
For example, say you have `Project` and `Task` models. A `Task` belongs to
a `Project` and `Project` has many `Tasks`. You would run this command:
```bash
blitz generate all task --parent project
```
which would generate the following files:
```
app/pages/projects/[projectId]/tasks/[taskId]/edit.tsx
app/pages/projects/[projectId]/tasks/[taskId].tsx
app/pages/projects/[projectId]/tasks/index.tsx
app/pages/projects/[projectId]/tasks/new.tsx
app/tasks/components/TaskForm.tsx
app/tasks/queries/getTask.ts
app/tasks/queries/getTasks.ts
app/tasks/mutations/createTask.ts
app/tasks/mutations/deleteTask.ts
app/tasks/mutations/updateTask.ts
```
Note that this will not generate the relationships between the models,
only the queries, mutations and pages.
##### `--dry-run`
Shorthand: `-d`
Displays what files would be generated but does not write the files to
disk.
#### `--env`
Shorthand: `-e`
Allows you to set app environment name.
[Read more](/docs/custom-environments#custom-environments).
### Basic Examples {#basic-examples}
```bash
blitz generate all project
```
```bash
blitz generate mutations project
```
```bash
blitz generate crud admin/topsecret/files
```
```bash
blitz generate pages tasks --parent=projects
```
## Model Generation {#model-generation}
All of the following commands will generate a model in your prisma schema
file:
- `blitz generate all`
- `blitz generate resource`
- `blitz generate model`
### Model Fields {#model-fields}
Model fields can be added like this:
```bash
blitz generate model [fieldName]:[type]:[attribute]
```
- `fieldName` is the name of your database column and can be anything
- Use `belongsTo` to add a model relationship, ex: `belongsTo:user`
- `type`
- **Optional** - defaults to `string` if not specified
- **Values:** `string`, `boolean`, `int`, `float`, `dateTime`, `json`,
`uuid`, or a model name
- `foo:uuid` is a shorthand syntax for `foo:string:default=uuid`
- Add `?` to make the type optional like this: `string?`
**Note:** If you use `zsh` in your terminal, you need to wrap a field
in quotes (`""`) to be correctly interpreted. For example:
`"name:string?"`
- Add `[]` to make the type a list like: `task[]`
- `attribute` is for adding a
[prisma field attribute](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/data-model#attributes)
- **Optional**
- Supported: `default`, `unique`
- If the attribute takes an argument, you can include it with `=` like:
`default=false`. That will set the default value to `false`
For more details, see the
[docs on Prisma scalar types](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference/#model-field-scalar-types)
or the
[docs on Prisma relations](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations).
##### Scalar Fields
```bash
> blitz generate model puppy isCute:boolean
> blitz generate model rocket launchedAt:dateTime
> blitz generate model task completed:boolean:default=false
```
##### Has One Relation
```bash
blitz g model project task:Task
```
will generate this:
```prisma
model Project {
...
task Task
}
```
##### Has Many Relation
```bash
blitz g model project tasks:Task[]
```
will generate this:
```prisma
model Project {
...
tasks Task[]
}
```
##### Belongs To Relation
```bash
blitz g model task belongsTo:project
```
will generate this:
```prisma
model Task {
...
project Project? @relation(fields: [projectId], references: [id])
projectId Int?
}
```
##### Full Example
```bash
blitz generate model task \
name \
completed:boolean:default=false \
belongsTo:project?
```
will generate this:
```prisma
model Task {
id Int @default(autoincrement()) @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
name String
completed Boolean @default(false)
project Project? @relation(fields: [projectId], references: [id])
projectId Int?
}
```
### Updating a model {#updating-a-model}
Running `blitz generate model` subsequent times will add fields to the
existing model. For example, the below command will add the `subheading`
field to the `Task` model.
```bash
blitz generate model task subheading:string
```
<!--
### Custom templates {#custom-templates}
If you want to use custom templates with `blitz generate` instead of the
default ones (e.g. with different styles), you can provide a path to the
local directory with template files. You can specify it in your
`blitz.config.ts` file. [Read more here](/docs/blitz-config#codegen). -->

View File

@@ -0,0 +1,66 @@
---
title: blitz install
sidebar_label: blitz install
---
**Alias: `blitz i`**
Use this command to install a Blitz Recipe into your project.
Supports both official recipes and custom third-party recipes. Recipe
names can be specified in any of the following formats:
- `blitz install` select and install from the list of official Blitz
recipes
- `blitz install official-recipe` for an official recipe from the Blitz
team
- `blitz install ./recipes/my-local-recipes.ts` for a locally-authored
recipe
- `blitz install github-user/my-published-recipe` for a custom recipe
hosted on GitHub
Also, you can pass arguments to recipe itself in the form of `key=value`
(if it accept them):
```bash
$ blitz install my-recipe key=value
```
For more information about recipes, see the official docs for
[installing recipes](using-recipes) and
[writing your own.](writing-recipes)
| Argument | Required | Description |
| -------- | -------- | ---------------------------------------------------------------------------------------- |
| `name` | No | The name of the recipe to install, a path to a local recipe, or a GitHub repository name |
#### Options
| Argument | Short | Description | Default |
| -------- | ----- | ------------------------------------------------------------------------------------- | ------- |
| `--yes` | `-y` | Install the recipe automatically without user confirmation. | `false` |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Example Output
```bash
> blitz install
? Select a recipe to install …
base-web
bumbag-ui
chakra-ui
emotion
### and more
```
```bash
> blitz install tailwind
✅ Installed 2 dependencies
✅ Successfully created postcss.config.js, tailwind.config.js
✅ Successfully created app/styles/button.css, app/styles/index.css
✅ Modified 1 file: app/pages/_app.tsx
🎉 The recipe for Tailwind CSS completed successfully! Its functionality is now fully configured in your Blitz app.
```

View File

@@ -0,0 +1,51 @@
---
title: blitz new
sidebar_label: blitz new
---
Generates a new Blitz project in the current directory. You can choose
between a full Blitz project and a minimal project. The former includes a
database setup, authentication, and all of the other configuration that
makes Blitz's magic work. The minimal template is a bare Blitz project
with a minimum set of dependecies. It has no database or auth included.
Both templates will include basic scaffolding for a landing page.
```bash
blitz new [name]
```
| Argument | Required | Description |
| -------- | -------- | ------------------------------------------------------------- |
| `name` | Yes | The project name. Will be used in initial project scaffolding |
Note: `blitz new .` will set the project name to the current directory's
name.
#### Options
| Argument | Short | Description | Default |
| ---------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `--template` | | Lets you choose a project's template. If omittied, it shows a prompt to choose the template. Options: `full`, `minimal`. | None |
| `--language` | | A new project's language. If omittied, it shows a prompt to choose the language. Options: `typescript`, `javascript`. | None |
| `--dry-run` | `-d` | Displays what files would be generated but does not write the files to disk. | `false` |
| `--no-git` | | Skips git repository creation. | `false` |
| `--skip-upgrade` | | Skip blitz upgrade if outdated. | `false` |
| `--form` | | A form library for the full project. If omitted, it shows a prompt to choose the library. Options: `react-final-form`, `react-hook-form`, `formik`. | None |
| `--yarn` | | Use yarn as the package manager. | `false` |
| `--npm` | | Use npm as the package manager. | `false` |
| `--pnpm` | | Use pnpm as the package manager. | `false` |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Examples
```bash
blitz new task-manager
```
```bash
blitz new task-manager --npm
```
```bash
blitz new landing-page --template=minimal
```

View File

@@ -0,0 +1,31 @@
---
title: Blitz CLI Overview
sidebar_label: Overview
---
The Blitz CLI is your single access point for interacting with your app,
from database management to code generation. It is opinionated and will
guide you towards best practices, while flexible enough to allow you to
build your project however you'd like.
### Installation {#installation}
You've likely already interacted with the Blitz CLI when setting up your
app. We recommend installing Blitz globally to ensure that you can quickly
run Blitz commands on-demand:
```bash
# npm
npm i -g blitz
# Yarn
yarn global add blitz
```
### Available Commands {#available-commands}
In the following sections you can learn about all of the CLI commands.
Additionally, you can run `blitz help` or `blitz [COMMAND] --help` to get
help directly in your terminal.

View File

@@ -0,0 +1,26 @@
---
title: blitz prisma
sidebar_label: blitz prisma
---
**Alias: `blitz p`**
This loads all your environment variables and then proxies all arguments
to
[the Prisma CLI](https://www.prisma.io/docs/reference/api-reference/command-reference).
Hopefully Prisma adds full env support so we can eventually remove this :)
#### Options
| Option | Shorthand | Description | Default |
| ------- | --------- | ------------------------------------------------------------------------------------- | ------- |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Examples
```
blitz prisma generate
blitz prisma migrate dev
blitz prisma migrate deploy
```

View File

@@ -0,0 +1,35 @@
---
title: blitz routes
sidebar_label: blitz routes
---
**Alias: `blitz r`**
Display all Blitz URL Routes
#### Examples
```bash
> blitz routes
✔ Compiled
┌──────┬─────────────────────────────────────┬───────────────────────────────────┬──────┐
│ HTTP │ Source File │ URI │ Type │
├──────┼─────────────────────────────────────┼───────────────────────────────────┼──────┤
│ GET │ app/pages/index.tsx │ / │ PAGE │
│ GET │ app/pages/ssr.tsx │ /ssr │ PAGE │
│ GET │ app/auth/pages/login.tsx │ /login │ PAGE │
│ GET │ app/auth/pages/signup.tsx │ /signup │ PAGE │
│ GET │ app/users/pages/users/index.tsx │ /users │ PAGE │
│ * │ app/api/auth/[...auth].ts │ /api/auth/[...auth] │ API │
│ POST │ app/auth/mutations/login.ts │ /api/auth/mutations/login │ RPC │
│ POST │ app/auth/mutations/logout.ts │ /api/auth/mutations/logout │ RPC │
│ POST │ app/auth/mutations/signup.ts │ /api/auth/mutations/signup │ RPC │
│ POST │ app/users/mutations/trackView.ts │ /api/users/mutations/trackView │ RPC │
│ POST │ app/users/queries/getCurrentUser.ts │ /api/users/queries/getCurrentUser │ RPC │
│ POST │ app/users/queries/getUser.ts │ /api/users/queries/getUser │ RPC │
│ POST │ app/users/queries/getUsers.ts │ /api/users/queries/getUsers │ RPC │
└──────┴─────────────────────────────────────┴───────────────────────────────────┴──────┘
✨ Done in 4.60s.
```

View File

@@ -0,0 +1,45 @@
---
title: blitz start
sidebar_label: blitz start
---
**Alias: `blitz s`**
Starts the Blitz production server.
This is a super thin wrapper over Next CLI to improve environment variable
loading.
```bash
blitz dev -e staging
```
It does the following:
1. Loads correct `.env.X` and `.env.X.local` files based on the `-e X`
flag that you specify. Ex: `-e staging` loads `.env.staging`.
2. Sets `process.env.APP_ENV` to the env name. Ex: `-e staging` =>
`APP_ENV=staging`.
3. Passes all other arguments directly to the next build command
#### Options
| Option | Shorthand | Description | Default |
| ------------ | --------- | ------------------------------------------------------------------------------------- | ------------- |
| `--hostname` | `-H` | Set the hostname to use for the server. | `"localhost"` |
| `--port` | `-p` | Set the port you'd like the server to listen on. | `3000` |
| `--inspect` | | Enable the Node.js inspector | `false` |
| `--env` | `-e` | Set app environment name. [Read more](/docs/custom-environments#custom-environments). | None |
#### Examples
<Card type="note">
Make sure to run `blitz build` before running `blitz start`
</Card>
```bash
blitz start
blitz start -H 127.0.0.1 -p 5632
```

View File

@@ -0,0 +1,32 @@
---
title: Client and Server
sidebar_label: Client and Server
---
Blitz is focused on making the communication between server and client
seamless. That means you can import and use server code directly in your
UI components. However, you might be wondering what runs on the server and
what runs on the client. Here's a quick overview.
![Architecture diagram](/img/architecture-alt.png)
#### Framework Adapters
Blitz offers adapters for frameworks that allow you to inject plugins like
the RPC or auth plugin. For more information regarding specific adapters
head to the associated section in the documenation.
[@blitzjs/next](/docs/blitzjs-next)
#### Queries and mutations
Queries and mutations only run on the server — at build time, the direct
function import is replaced with an RPC network call. So the query
function code is never included in your client code. It's instead moved to
an API layer.
#### React Components
Some data doesn't exist on the client's first render (e.g. session), which
causes a quick "flash". You can use
`Page.suppressFirstRenderFlicker = true` to hide the first render.

View File

@@ -0,0 +1,117 @@
---
id: code-of-conduct
title: The Blitz Community Code of Conduct
sidebar_label: Code of Conduct
---
<!--alex disable racist sexual nuts-->
The Blitz core members take this CoC very serious. All members,
contributors and volunteers in this community are required to act
according to the following Code of Conduct to keep Blitz a positive,
growing project and community and help us provide and ensure a safe
environment for everyone.
## When Something Happens {#when-something-happens}
If you see a Code of Conduct violation, follow these steps:
1. Let the person know that what they did is not appropriate and ask them
to stop and/or edit their message(s).
2. That person should immediately stop the behavior and correct the issue.
3. If this doesnt happen, or if youre uncomfortable speaking up, contact
Brandon Bayer ([Twitter](https://twitter.com/flybayer) |
[Email](mailto:b@bayer.ws)).
When reporting, please include any relevant details, links, screenshots,
context, or other information that may be used to better understand and
resolve the situation.
The core members will prioritize the well-being and comfort of the
recipients of the violation over the comfort of the violator.
## What We Believe and How We Act {#what-we-believe-and-how-we-act}
- We are committed to providing a friendly, safe and welcoming environment
for everyone, regardless of age, body size, culture, ethnicity, gender
expression, gender identity, level of experience, nationality, personal
ability or disability, physical appearance, physical or mental
difference, race, religion, set of skills, sexual orientation,
socio-economic status, and subculture. We welcome people regardless of
these or other attributes.
- We are better together. We are more alike than different.
- Our community is based on mutual respect, tolerance, and encouragement.
- We believe that a diverse community where people treat each other with
respect is stronger, more vibrant and has more potential contributors
and more sources for ideas. We aim for more diversity.
- We are kind, welcoming and courteous to everyone.
- Were respectful of others, their positions, their skills, their
commitments and their efforts.
- Were attentive in our communications, whether in person or online, and
were tactful when approaching differing views.
- We are aware that language shapes reality. Thus, we use inclusive,
gender-neutral language in the documents we provide and when we talk to
people. When referring to a group of people, we aim to use
gender-neutral terms like “team”, “folks”, “everyone”. (For details, we
recommend
[this post](https://modelviewculture.com/pieces/gendered-language-feature-or-bug-in-software-documentation)).
- We respect that people have differences of opinion and criticize
constructively.
- We value people over code.
## Don'ts {#don-ts}
- Dont discriminate against anyone.
- Sexism and racism of any kind (including sexist and racist “jokes”),
demeaning or insulting behaviour and harassment are seen as direct
violations to this Code of Conduct. Harassment includes offensive verbal
comments related to age, body size, culture, ethnicity, gender
expression, gender identity, level of experience, nationality, personal
ability or disability, physical appearance, physical or mental
difference, race, religion, set of skills, sexual orientation,
socio-economic status, and subculture. Harassment also includes sexual
images in public spaces, deliberate intimidation, stalking, following,
harassing photography or recording, inappropriate physical contact, and
unwelcome sexual attention.
- On Discord and other online or offline communications channels, don't
use overtly sexual nicknames or other nicknames that might detract from
a friendly, safe and welcoming environment for all.
- Dont be mean or rude.
- Respect that some individuals and cultures consider the casual use of
profanity offensive and off-putting.
- Unwelcome / non-consensual sexual advances over Discord or any other
channels related with this community are not okay.
- Derailing, tone arguments and otherwise playing on peoples desires to
be nice are not welcome, especially in discussions about violations to
this Code of Conduct.
- Please avoid unstructured critique.
- Likewise any spamming, trolling, flaming, baiting or other
attention-stealing behavior is not welcome.
- Sponsors of Blitz are also subject to this Code of Conduct. In
particular, sponsors are required to not use sexualized images,
activities, or other material which is not according to this Code of
Conduct.
## Consequences for Violations to this Code of Conduct {#consequences-for-violations-to-this-code-of-conduct}
If a participant engages in any behavior violating this Code of Conduct,
the core members of this community will take any action they deem
appropriate, starting with a gentle warning and then escalating as needed
to expulsion from the community, exclusion from any interaction and loss
of all rights in this community.
## Decisions About Consequences of Violations {#decisions-about-consequences-of-violations}
Decisions about consequences of violations of this Code of Conduct are
made by this communitys core members and may not be discussed with the
person responsible for the violation.
## For Questions or Feedback {#for-questions-or-feedback}
If you have any questions or feedback on this Code of Conduct, were happy
to hear from you.
## Thanks for Inspiration {#thanks-for-inspiration}
- [Hoodie](https://github.com/hoodiehq/hoodie)
- [WeAllJS](https://wealljs.org/code-of-conduct)

View File

@@ -0,0 +1,152 @@
---
title: Code Splitting
sidebar_label: Code Splitting
---
Blitz automatically optimizes your JavaScript bundles for each page, so
your users will only download the required assets for a given route
(without any extra effort on your part!). This technique is known as
[code splitting](https://reactjs.org/docs/code-splitting.html).
In the event you need to split or lazy-load assets within a page, Blitz
supports ES2020
[dynamic `import()`](https://github.com/tc39/proposal-dynamic-import) for
JavaScript. With it you can import JavaScript modules (React components,
libraries, etc.) dynamically and work with them. You can think of dynamic
imports as another way to split your code into manageable chunks.
One reason you may want to do this is when a feature on page relies on a
very large library that you don't want the user's browser to have to
download unless they need it (and not all visitors to the page will need
it). You can use dynamic imports to load the library only after the user
has clicked a button or toggled an option somewhere on the page, for
example. This helps keep the initial page load nice and fast for everyone.
Another scenario that may benefit from dynamic imports is using a library
that only works in the browser (perhaps it uses the `window` object
without first checking if it's available). In this case, dynamically
importing the library will prevent it from throwing an error when Blitz
performs server-side rendering.
## Basic usage {#basic-usage}
In the following example, the module `../components/hello` will be
dynamically loaded by the page:
```jsx
import {dynamic} from "blitz"
const DynamicComponent = dynamic(() => import("../components/hello"))
function Home() {
return (
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
```
`DynamicComponent` will be the default component returned by
`../components/hello`. It works like a regular React Component, and you
can pass props to it as you normally would.
> **Note**: In `import('path/to/component')`, the path must be explicitly
> written. It can't be a template string nor a variable. Furthermore the
> `import()` has to be inside the `dynamic()` call for Next.js to be able
> to match webpack bundles / module ids to the specific `dynamic()` call
> and preload them before rendering. `dynamic()` can't be used inside of
> React rendering as it needs to be marked in the top level of the module
> for preloading to work, similar to `React.lazy`.
## With named exports {#with-named-exports}
If the dynamic component is not the default export, you can use a named
export too. Consider the module `../components/hello.js` which has a named
export `Hello`:
```jsx
export function Hello() {
return <p>Hello!</p>
}
```
To dynamically import the `Hello` component, you can return it from the
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
returned by
[`import()`](https://github.com/tc39/proposal-dynamic-import#example),
like so:
```jsx
import {dynamic} from "blitz"
const DynamicComponent = dynamic(() => import("../components/hello").then((mod) => mod.Hello))
function Home() {
return (
<div>
<Header />
<DynamicComponent />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
```
## With custom loading component {#with-custom-loading-component}
An optional `loading` component can be added to render a loading state
while the dynamic component is being loaded. For example:
```jsx
import {dynamic} from "blitz"
const DynamicComponentWithCustomLoading = dynamic(() => import("../components/hello"), {
loading: () => <p>...</p>,
})
function Home() {
return (
<div>
<Header />
<DynamicComponentWithCustomLoading />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
```
## With no SSR {#with-no-ssr}
You may not always want to include a module on server-side. For example,
when the module includes a library that only works in the browser.
Take a look at the following example:
```jsx
import {dynamic} from "blitz"
const DynamicComponentWithNoSSR = dynamic(() => import("../components/hello3"), {
ssr: false,
})
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
export default Home
```

Some files were not shown because too many files have changed in this diff Show More