Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dynamically set meta tags #1453

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/arb-token-bridge-ui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ yarn-error.log*
# Next.js
.next
next-env.d.ts

# auto-generated images
public/images/__auto-generated
Binary file not shown.
5 changes: 4 additions & 1 deletion packages/arb-token-bridge-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@
"zustand": "^4.3.9"
},
"scripts": {
"predev": "yarn generateDenylist && yarn generateOpenGraphImages",
"dev": "next dev",
"prebuild": "yarn generateDenylist",
"prebuild": "yarn generateDenylist && yarn generateOpenGraphImages",
"postinstall": "patch-package",
"build": "next build",
"start": "next start",
Expand All @@ -62,6 +63,7 @@
"lint:fix": "tsc && eslint 'src/**/*.{js,ts,tsx}' --quiet --fix",
"prettier:format": "prettier --config-precedence file-override --write \"src/**/*.{tsx,ts,scss,md,json}\"",
"generateDenylist": "ts-node --project ./scripts/tsconfig.json ./scripts/generateDenylist.ts",
"generateOpenGraphImages": "ts-node --project ./scripts/tsconfig.json ./src/generateOpenGraphImages.tsx",
"generateCoreChainsToMonitor": "ts-node --project ./scripts/tsconfig.json ./scripts/generateCoreChainsToMonitor.ts",
"generateOrbitChainsToMonitor": "ts-node --project ./scripts/tsconfig.json ./scripts/generateOrbitChainsToMonitor.ts"
},
Expand Down Expand Up @@ -112,6 +114,7 @@
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.11",
"satori": "^0.10.11",
"start-server-and-test": "^2.0.0",
"tailwindcss": "^3.2.4",
"ts-node": "^10.9.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/arb-token-bridge-ui/scripts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"compilerOptions": {
// typescript options here
"moduleResolution": "NodeNext"
"moduleResolution": "NodeNext",
"jsx": "react"
}
}
170 changes: 170 additions & 0 deletions packages/arb-token-bridge-ui/src/generateOpenGraphImages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from 'react'
import satori, { Font } from 'satori'
import sharp from 'sharp'
import fs from 'fs'

const dimensions = {
width: 1200,
height: 627
} as const

async function getFont(): Promise<Font> {
const spaceGrotesk = fs.readFileSync('./SpaceGrotesk-Medium.ttf')

return {
name: 'Space Grotesk',
data: spaceGrotesk,
weight: 500,
style: 'normal'
}
}

type Chain = {
name: string
slug: string
logo: string
}

type ChainCombination = [Chain, Chain]

const configs: ChainCombination[] = [
[
{
name: 'Ethereum',
slug: 'ethereum',
logo: 'https://l2beat.com/icons/ethereum.png'
},
{
name: 'Arbitrum One',
slug: 'arbitrum-one',
logo: 'https://l2beat.com/icons/arbitrum.png'
}
],
[
{
name: 'Ethereum',
slug: 'ethereum',
logo: 'https://l2beat.com/icons/ethereum.png'
},
{
name: 'Arbitrum Nova',
slug: 'arbitrum-nova',
logo: 'https://l2beat.com/icons/nova.png'
}
],
[
{
name: 'Arbitrum One',
slug: 'arbitrum-one',
logo: 'https://l2beat.com/icons/arbitrum.png'
},
{
name: 'Xai',
slug: 'xai',
logo: 'https://bin.bnbstatic.com/static/research/xai.png'
}
]
]

function ChainWrapper({ chain }: { chain: Chain }) {
return (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '6px'
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
background: 'rgba(0,0,0, 1)',
borderRadius: '50%',
padding: '8px'
}}
>
<img src={chain.logo} width={38} height={38} />
</div>
{chain.name}
</div>
)
}

async function generateSvg({ from, to }: { from: Chain; to: Chain }) {
const font = await getFont()

const svg = await satori(
//
<div
style={{
...dimensions,
display: 'flex',
position: 'relative',
width: dimensions.width,
height: dimensions.height
}}
>
<img
src="https://bridge.arbitrum.io/og-image.jpg"
width={1200}
height={627}
alt="Arbitrum Bridge"
/>
<div
style={{
...dimensions,
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'flex-end',
padding: '0 85px 90px'
}}
>
<div
style={{
display: 'flex',
gap: '45px',
width: '100%',
height: 90,
alignItems: 'center',
justifyContent: 'flex-start',
fontSize: '40px',
fontWeight: '500',
color: 'white'
}}
>
<ChainWrapper chain={from} />
<ChainWrapper chain={to} />
</div>
</div>
</div>,
{
...dimensions,
fonts: [font]
}
)

const file = `${from.slug}-to-${to.slug}.jpg`
const filePath = `./public/images/__auto-generated/open-graph/${file}`

await sharp(Buffer.from(svg))
.jpeg({ quality: 90, mozjpeg: true })
.toFile(filePath)

console.log(`Generated ${filePath}`)
}

async function main() {
for (const combination of configs) {
await generateSvg({ from: combination[0], to: combination[1] })
await generateSvg({ from: combination[1], to: combination[0] })
}
}

main()
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import {

import {
ChainKeyQueryParam,
getChainForChainKeyQueryParam,
getChainQueryParamForChain,
isValidChainQueryParam
} from '../types/ChainQueryParam'
import { ChainId } from '../util/networks'
import { getChainForChainKeyQueryParam } from '../util/chainQueryParamUtils'

export enum AmountQueryParamEnum {
MAX = 'max'
Expand Down
79 changes: 76 additions & 3 deletions packages/arb-token-bridge-ui/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ import '@rainbow-me/rainbowkit/styles.css'

import { registerLocalNetwork } from '../util/networks'
import { Layout } from '../components/common/Layout'
import { siteTitle } from './_document'

import '../styles/tailwind.css'
import '../styles/purple.css'
import { ChainKeyQueryParam } from '../types/ChainQueryParam'
import { isUserRejectedError } from '../util/isUserRejectedError'
import { getChainForChainKeyQueryParam } from '../util/chainQueryParamUtils'

const siteTitle = 'Bridge to Arbitrum'

if (
process.env.NODE_ENV !== 'production' ||
Expand Down Expand Up @@ -83,12 +86,82 @@ if (
})
}

export default function App({ Component, pageProps }: AppProps) {
type ChainBlob = {
name: string
slug: string
}

function DynamicMetaData({
sourceChainSlug,
destinationChainSlug
}: {
sourceChainSlug: ChainKeyQueryParam
destinationChainSlug: ChainKeyQueryParam
}) {
const sourceChain: ChainBlob = {
name: getChainForChainKeyQueryParam(sourceChainSlug).name,
slug: sourceChainSlug
}
const destinationChain: ChainBlob = {
name: getChainForChainKeyQueryParam(destinationChainSlug).name,
slug: destinationChainSlug
}

const siteDescription = `Bridge from ${sourceChain.name} to ${destinationChain.name} using the Arbitrum Bridge. Built to scale Ethereum, Arbitrum brings you 10x lower costs while inheriting Ethereum’s security model. Arbitrum is a Layer 2 Optimistic Rollup.`
const siteDomain = 'https://bridge.arbitrum.io'

return (
<>
<title>{siteTitle}</title>
<meta name="description" content={siteDescription} />

{/* <!-- Facebook Meta Tags --> */}
<meta name="og:url" property="og:url" content={siteDomain} />
<meta name="og:type" property="og:type" content="website" />
<meta name="og:title" property="og:title" content={siteTitle} />
<meta
name="og:description"
property="og:description"
content={siteDescription}
/>
<meta
name="og:image"
property="og:image"
content={`${siteDomain}/images/__auto-generated/open-graph/${sourceChain.slug}-to-${destinationChain.slug}.jpg`}
/>

{/* <!-- Twitter Meta Tags --> */}
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:domain"
property="twitter:domain"
content="bridge.arbitrum.io"
/>
<meta name="twitter:url" property="twitter:url" content={siteDomain} />
<meta name="twitter:title" content={siteTitle} />
<meta name="twitter:description" content={siteDescription} />
<meta
name="twitter:image"
content={`${siteDomain}/images/__auto-generated/open-graph/${sourceChain.slug}-to-${destinationChain.slug}.jpg`}
/>
</>
)
}

export default function App({ Component, pageProps, router }: AppProps) {
const sourceChainSlug = (router.query.sourceChain?.toString() ??
'ethereum') as ChainKeyQueryParam
const destinationChainSlug = (router.query.destinationChain?.toString() ??
'arbitrum-one') as ChainKeyQueryParam

return (
<>
<Head>
<DynamicMetaData
sourceChainSlug={sourceChainSlug}
destinationChainSlug={destinationChainSlug}
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{siteTitle}</title>
</Head>
<Layout>
<Component {...pageProps} />
Expand Down
20 changes: 0 additions & 20 deletions packages/arb-token-bridge-ui/src/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { Html, Head, Main, NextScript } from 'next/document'

export const siteTitle = 'Bridge to Arbitrum'
const siteDomain = 'https://bridge.arbitrum.io'
const siteDescription =
'Built to scale Ethereum, Arbitrum brings you 10x lower costs while inheriting Ethereum’s security model. Arbitrum is a Layer 2 Optimistic Rollup.'

export default function Document() {
return (
<Html lang="en">
Expand All @@ -13,21 +8,6 @@ export default function Document() {
<link rel="icon" href="/logo.png" />

<meta name="theme-color" content="#000000" />
<meta name="description" content={siteDescription} />
{/* <!-- Facebook Meta Tags --> */}
<meta property="og:url" content={siteDomain} />
<meta property="og:type" content="website" />
<meta property="og:title" content={siteTitle} />
<meta property="og:description" content={siteDescription} />
<meta property="og:image" content={`${siteDomain}/og-image.jpg`} />

{/* <!-- Twitter Meta Tags --> */}
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:domain" content="bridge.arbitrum.io" />
<meta property="twitter:url" content={siteDomain} />
<meta name="twitter:title" content={siteTitle} />
<meta name="twitter:description" content={siteDescription} />
<meta name="twitter:image" content={`${siteDomain}/og-image.jpg`} />
</Head>
<body>
<Main />
Expand Down
Loading
Loading