Skip to content
Merged
Changes from 5 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
102 changes: 99 additions & 3 deletions frontend/src/app/organizations/[organizationKey]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Metadata } from 'next'
import Script from 'next/script'
import React from 'react'
import { apolloClient } from 'server/apolloClient'
import { GET_ORGANIZATION_METADATA } from 'server/queries/organizationQueries'
import {
GET_ORGANIZATION_METADATA,
GET_ORGANIZATION_DATA,
} from 'server/queries/organizationQueries'
import { generateSeoMetadata } from 'utils/metaconfig'

export async function generateMetadata({
Expand All @@ -28,6 +32,98 @@ export async function generateMetadata({
: null
}

export default function OrganizationDetailsLayout({ children }: { children: React.ReactNode }) {
return children
async function generateOrganizationStructuredData(organizationKey: string) {
// https://developers.google.com/search/docs/appearance/structured-data/organization
// https://schema.org/Organization

try {
const { data } = await apolloClient.query({
query: GET_ORGANIZATION_DATA,
variables: {
login: organizationKey,
},
})

const organization = data?.organization
if (!organization) return null

const structuredData = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add email as a top level field?

@rudransh-shrivastava rudransh-shrivastava Aug 4, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

'@context': 'https://schema.org' as const,
'@type': 'Organization' as const,
name: organization.name || organization.login,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you order the attributes alphabetically?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

description: organization.description,
url: `https://nest.owasp.org/organizations/${organizationKey}`,
logo: organization.avatarUrl
? {
'@type': 'ImageObject' as const,
url: organization.avatarUrl,
}
: undefined,
foundingDate: organization.createdAt,
location: organization.location
? {
'@type': 'Place' as const,
name: organization.location,
}
: undefined,
contactPoint: organization.email
? {
'@type': 'ContactPoint' as const,
email: organization.email,
contactType: 'general inquiry',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this recommended somewhere?

@rudransh-shrivastava rudransh-shrivastava Aug 4, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly, contactType is of type Text:A person or organization can have different contact points, for different purposes. For example, a sales contact point, a PR contact point and so on. This property is used to specify the kind of contact point.

general inquiry seemed okay to me
Contact Type Doc

}
: undefined,
sameAs: [organization.url],
memberOf: {
'@type': 'Organization' as const,
name: 'OWASP Foundation',
url: 'https://nest.owasp.org',
},
keywords: [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this part of a broader schema?

@rudransh-shrivastava rudransh-shrivastava Aug 4, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, its part of Thing schema, Organization inherits from it.
Keyword Doc

organization.name,
organization.login,
'cybersecurity',
'application security',
'open source',
'OWASP',
]
.filter(Boolean)
.join(', '),
}

// Remove undefined properties
Object.keys(structuredData).forEach(
(key) => structuredData[key] === undefined && delete structuredData[key]
)

return structuredData
} catch {
return null
}
Comment thread
arkid15r marked this conversation as resolved.
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export default async function OrganizationDetailsLayout({
children,
params,
}: Readonly<{
children: Readonly<React.ReactNode>
params: Promise<{ organizationKey: string }>
}>) {
const { organizationKey } = await params
const structuredData = await generateOrganizationStructuredData(organizationKey)

return (
<>
{structuredData && (
<Script
id="organization-structured-data"
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(structuredData, null, 2),
}}
/>
)}
{children}
</>
)
}