-
-
Notifications
You must be signed in to change notification settings - Fork 291
Added Metadata for dynamic pages and static pages #1414
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
Changes from 9 commits
10453c4
79d50d2
217e383
8af2bfd
50e5fea
c3e2d54
290f012
2ebd145
4d47515
a9cb9e8
c9f23e5
648492a
c8cf218
a52be2c
94e4e0c
c3531fb
5a1c53f
0087207
4a8296f
7761463
f740d4e
0dcba21
c400b88
71d984c
af24fa9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { getStaticMetadata } from 'utils/metaconfig' | ||
|
|
||
| export const metadata: Metadata = getStaticMetadata('about', '/about') | ||
|
|
||
| export default function AboutLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { GET_CHAPTER_DATA } from 'server/queries/chapterQueries' | ||
| import { apolloServerClient } from 'utils/helpers/apolloClientServer' | ||
| import { generateSeoMetadata } from 'utils/metaconfig' | ||
|
|
||
| type Params = Promise<{ chapterKey: string }> | ||
|
|
||
| export async function generateMetadata({ params }: { params: Params }): Promise<Metadata> { | ||
| try { | ||
| const { chapterKey } = await params | ||
| const { data } = await apolloServerClient.query({ | ||
| query: GET_CHAPTER_DATA, | ||
| variables: { | ||
| key: chapterKey, | ||
| }, | ||
| }) | ||
| const chapter = data?.chapter | ||
| if (!chapter) { | ||
| return | ||
| } | ||
| return generateSeoMetadata({ | ||
| title: chapter.name, | ||
| description: chapter.summary ?? 'Discover details about this OWASP chapter.', | ||
| canonicalPath: `/chapter/${chapterKey}`, | ||
| keywords: ['owasp', 'security', 'chapter', chapterKey, chapter.name], | ||
| }) | ||
| } catch { | ||
| return | ||
| } | ||
| } | ||
|
|
||
| export default function ChapterDetailsLayout({ | ||
| children, | ||
| }: Readonly<{ | ||
| children: React.ReactNode | ||
| }>) { | ||
| return <div className="chapter-layout">{children}</div> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { getStaticMetadata } from 'utils/metaconfig' | ||
|
|
||
| export const metadata: Metadata = getStaticMetadata('chapters', '/chapters') | ||
|
|
||
| export default function ChaptersLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { GET_COMMITTEE_DATA } from 'server/queries/committeeQueries' | ||
| import { apolloServerClient } from 'utils/helpers/apolloClientServer' | ||
| import { generateSeoMetadata } from 'utils/metaconfig' | ||
|
|
||
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ committeeKey: string }> | ||
| }): Promise<Metadata> { | ||
| try { | ||
| const { committeeKey } = await params | ||
| const { data } = await apolloServerClient.query({ | ||
| query: GET_COMMITTEE_DATA, | ||
| variables: { | ||
| key: committeeKey, | ||
| }, | ||
| }) | ||
| const committee = data?.committee | ||
| if (!committee) { | ||
| return | ||
| } | ||
| return generateSeoMetadata({ | ||
| title: committee.name, | ||
| description: committee.summary ?? 'Discover details about this OWASP committee.', | ||
| canonicalPath: `/committees/${committeeKey}`, | ||
| keywords: ['owasp', 'security', 'committee', committeeKey, committee.name], | ||
| }) | ||
| } catch { | ||
| return | ||
| } | ||
| } | ||
|
|
||
| export default function CommitteeDetailsLayout({ | ||
| children, | ||
| }: Readonly<{ | ||
| children: React.ReactNode | ||
| }>) { | ||
| return <div className="committee-layout">{children}</div> | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { getStaticMetadata } from 'utils/metaconfig' | ||
|
|
||
| export const metadata: Metadata = getStaticMetadata('committees', '/committees') | ||
|
|
||
| export default function CommitteesLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import React from 'react' | ||
| import { getStaticMetadata } from 'utils/metaconfig' | ||
|
|
||
| export const metadata = getStaticMetadata('contribute', '/contribute') | ||
|
|
||
| export default function ContributeLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { GET_USER_DATA } from 'server/queries/userQueries' | ||
| import { apolloServerClient } from 'utils/helpers/apolloClientServer' | ||
| import { generateSeoMetadata } from 'utils/metaconfig' | ||
|
|
||
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ memberKey: string }> | ||
| }): Promise<Metadata> { | ||
| try { | ||
| const { memberKey } = await params | ||
| const { data } = await apolloServerClient.query({ | ||
| query: GET_USER_DATA, | ||
| variables: { | ||
| key: memberKey, | ||
| }, | ||
| }) | ||
| const user = data?.user | ||
| if (!user) { | ||
| return | ||
| } | ||
| return generateSeoMetadata({ | ||
| title: user.name ?? user.login, | ||
| description: user.bio ?? 'Discover details about this OWASP community member.', | ||
| canonicalPath: `/members/${memberKey}`, | ||
| keywords: [user.login, user.name, 'owasp', 'owasp community member'], | ||
| }) | ||
| } catch { | ||
| return | ||
| } | ||
| } | ||
|
|
||
| export default function UserDetailsLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { getStaticMetadata } from 'utils/metaconfig' | ||
|
|
||
| export const metadata: Metadata = getStaticMetadata('members', '/members') | ||
|
|
||
| export default function UsersLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { GET_ORGANIZATION_DATA } from 'server/queries/organizationQueries' | ||
|
||
| import { apolloServerClient } from 'utils/helpers/apolloClientServer' | ||
| import { generateSeoMetadata } from 'utils/metaconfig' | ||
|
|
||
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ organizationKey: string }> | ||
| }): Promise<Metadata> { | ||
| try { | ||
| const { organizationKey } = await params | ||
| const { data } = await apolloServerClient.query({ | ||
| query: GET_ORGANIZATION_DATA, | ||
| variables: { | ||
| login: organizationKey, | ||
| }, | ||
| }) | ||
| const organization = data?.organization | ||
| if (!organization) { | ||
| return | ||
| } | ||
| return generateSeoMetadata({ | ||
| title: organization?.name ?? organization?.login, | ||
| description: organization?.description ?? 'Discover details about this OWASP organization.', | ||
| canonicalPath: `/organizations/${organizationKey}`, | ||
| }) | ||
| } catch { | ||
| return | ||
| } | ||
| } | ||
|
|
||
| export default function OrganizationDetailsLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { GET_REPOSITORY_DATA } from 'server/queries/repositoryQueries' | ||
|
||
| import { apolloServerClient } from 'utils/helpers/apolloClientServer' | ||
| import { generateSeoMetadata } from 'utils/metaconfig' | ||
|
|
||
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ | ||
| repositoryKey: string | ||
| organizationKey: string | ||
| }> | ||
| }): Promise<Metadata> { | ||
| try { | ||
| const { repositoryKey, organizationKey } = await params | ||
| const { data } = await apolloServerClient.query({ | ||
| query: GET_REPOSITORY_DATA, | ||
| variables: { repositoryKey: repositoryKey, organizationKey: organizationKey }, | ||
| }) | ||
| const repository = data?.repository | ||
| if (!repository) { | ||
| return | ||
| } | ||
| return generateSeoMetadata({ | ||
| title: repository.name, | ||
| description: repository.description ?? 'Discover details about this OWASP repository.', | ||
| canonicalPath: `/organizations/${organizationKey}/repositories/${repositoryKey}`, | ||
| keywords: ['owasp', 'repository', repositoryKey, repository.name], | ||
| }) | ||
| } catch { | ||
| return | ||
| } | ||
| } | ||
|
|
||
| export default function RepositoryDetailsLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { getStaticMetadata } from 'utils/metaconfig' | ||
|
|
||
| export const metadata: Metadata = getStaticMetadata('organizations', '/organizations') | ||
|
|
||
| export default function OrganizationsLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { GET_PROJECT_DATA } from 'server/queries/projectQueries' | ||
| import { apolloServerClient } from 'utils/helpers/apolloClientServer' | ||
| import { generateSeoMetadata } from 'utils/metaconfig' | ||
|
|
||
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ | ||
| projectKey: string | ||
| }> | ||
| }): Promise<Metadata> { | ||
| try { | ||
| const { projectKey } = await params | ||
| const { data } = await apolloServerClient.query({ | ||
| query: GET_PROJECT_DATA, | ||
| variables: { | ||
| key: projectKey, | ||
| }, | ||
| }) | ||
| const project = data?.project | ||
| if (!project) { | ||
| return | ||
| } | ||
| return generateSeoMetadata({ | ||
| title: project.name, | ||
| description: project.summary ?? 'Discover details about this OWASP project.', | ||
| canonicalPath: `/projects/${projectKey}`, | ||
| keywords: ['owasp', 'project', projectKey, project.name], | ||
| }) | ||
| } catch { | ||
| return | ||
| } | ||
| } | ||
|
|
||
| export default function ProjectDetailsLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { getStaticMetadata } from 'utils/metaconfig' | ||
|
|
||
| export const metadata: Metadata = getStaticMetadata('projects', '/projects') | ||
|
|
||
| export default function ProjectsLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { Metadata } from 'next' | ||
| import React from 'react' | ||
| import { GET_SNAPSHOT_DETAILS } from 'server/queries/snapshotQueries' | ||
| import { apolloServerClient } from 'utils/helpers/apolloClientServer' | ||
| import { generateSeoMetadata } from 'utils/metaconfig' | ||
|
|
||
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ id: string }> | ||
| }): Promise<Metadata> { | ||
| try { | ||
| const { id: snapshotKey } = await params | ||
| const { data } = await apolloServerClient.query({ | ||
| query: GET_SNAPSHOT_DETAILS, | ||
| variables: { key: snapshotKey }, | ||
| }) | ||
| const snapshot = data?.snapshot | ||
| if (!snapshot) { | ||
| return | ||
| } | ||
| return generateSeoMetadata({ | ||
| title: snapshot?.title, | ||
| description: `Discover details about ${snapshot?.title} OWASP snapshot.`, | ||
| canonicalPath: `/snapshots/${snapshotKey}`, | ||
| keywords: ['owasp', 'snapshot', snapshotKey, snapshot?.title], | ||
| }) | ||
| } catch { | ||
| return | ||
| } | ||
| } | ||
| export default function SnapshotDetailsLayout({ children }: { children: React.ReactNode }) { | ||
| return children | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Docker-specific URLs and environment change
The addition of Docker-specific URLs and changing the default environment to 'docker' supports the server-side rendering infrastructure, but this could affect non-Docker development setups.
Also applies to: 6-6
🌐 Web query:
💡 Result:
Standard Practice for Managing Multiple Environment Configurations in Next.js
Managing multiple environment configurations is critical in Next.js applications to ensure security, proper functionality, and ease of deployment across development, testing, staging, and production. The Next.js ecosystem provides a clear, standardized approach for handling this.
Environment-Specific .env Files
.envor.env.localfor local development.env.developmentfor development.env.testfor testing.env.productionfor productionNODE_ENV(e.g., settingNODE_ENV=productionloads.env.production) [4][5][10]..env.local), while placeholders or defaults can be provided in.env.example[8].Public vs Private Environment Variables
NEXT_PUBLIC_(e.g.,NEXT_PUBLIC_API_URL). These are exposed to both server and client [7][8][10].Accessing Variables in Code
process.env, for example:Dynamic Configurations with next.config.js
next.config.jsfor programmatic configuration:phasefromnext/constantsto target specific environments:next.config.jscan also be exposed to the browser if set in theenvfield or by using Webpack'sDefinePlugin[2][3][4].Scripts for Clear Separation
package.jsonto help visually and operationally separate environment commands:Best Practices Summary
.env.local) to version control.NEXT_PUBLIC_only for variables safe to expose to the browser.By following these practices, teams can confidently manage environment-specific configurations in Next.js, enabling safer, more predictable, and easier-to-maintain deployments across multiple environments [4][5][10].
Citations:
Align environment configurations with Next.js recommended .env conventions
To avoid impacting non‑Docker development and follow Next.js best practices:
frontend/.env.exampleso it remains a generic template of all required keys..env.local(ignored in VCS) for your local overrides.env.developmentfor standard development (non‑Docker).env.docker(or extend your Docker Compose) to holdNEXT_PUBLIC_CSRF_URL_DOCKERandNEXT_PUBLIC_ENVIRONMENT=docker.env.productionfor production defaultsNODE_ENVor a custom script to load the correct .env file. For example inpackage.json:By splitting configs this way, you keep
.env.exampleenvironment‑agnostic and ensure developers only pick up Docker settings when explicitly intended.