diff --git a/.gitignore b/.gitignore index d014e4a..8188564 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,36 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ -/.vscode/ -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ +/.vscode/ +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts +config.bat diff --git a/app/[[...slug]]/page.js b/app/[[...slug]]/page.js index 4a4c306..09370e8 100644 --- a/app/[[...slug]]/page.js +++ b/app/[[...slug]]/page.js @@ -1,114 +1,114 @@ -import { PageAsset } from "@/components/page-asset"; -import { ErrorPage } from "@/components/error"; -import { getDotCMSPage } from "@/util/getDotCMSPage"; -import { getNavSections } from "@/services/docs/getNavSections"; -import { getSideNav } from "@/services/docs/getSideNav"; -import { BlockPageAsset } from "@/components/page-asset-with-content-block"; -/** - * Generate metadata - * - * @export - * @param {*} { params, searchParams } - * @return {*} - */ -export async function generateMetadata({ params }) { - const finalParams = await params; - const path = finalParams?.slug?.join("/") || "/"; - - try { - const pageContent = await getDotCMSPage(path); - if (!pageContent) { - return { - title: "not found", - }; - } - - const { pageAsset } = pageContent; - const page = pageAsset.page; - - const title = page?.friendlyName || page?.title; - - const description = page?.description || page?.teaser || page?.seoDescription || "dotCMS Dev Site, Documentation and Resources. Learn how to build with dotCMS"; - const hostname = "https://dev.dotcms.com"; - const keywords = page?.tags ? page?.tags.join(", ") : "dotcms, dotcms documentation, learn dotcms, dotcms api, dotcms dev, dotcms developer, dotcms developer documentation, dotcms developer api, dotcms developer documentation, dotcms developer api"; - - return { - title: title, - description: description, - url: `${hostname}${path}`, - siteName: 'dotCMS Docs', - keywords: keywords, - alternates: { - canonical: `${hostname}${path}`, - }, - metadataBase: new URL(hostname), - images: [{ - url: `${hostname}/dA/4b13a794db115b14ce79d30850712188/1024maxw/80q/}`, - width: 1200, - height: 630, - alt: description || title, - }], - locale: 'en_US', - type: 'article', - }; - } catch (e) { - console.error('Error generating metadata:', e?.message || e); - return { - title: "not found", - }; - } -} - -export default async function Page({ params }) { - const finalParams = await params; - - const path = finalParams?.slug?.join("/") || "/"; - const pageContent = await getDotCMSPage(path); - - - if (!pageContent) { - return ; - } - - const { pageAsset } = pageContent; - const isBlockPage = pageAsset?.page?.contentType === "BlockPage" - - if (isBlockPage) { - // Fetch navigation data (reuse cached nav sections instead of separate API call) - const [searchData, navSections] = await Promise.all([ - getSideNav(), - getNavSections({ path: '/docs/nav', depth: 4, languageId: 1, ttlSeconds: 600 }) - ]); - - // Extract the first segment of the URL to find the matching nav section - const pathParts = pageAsset?.page?.url.split("/").filter(part => part.length > 0); - const firstSegment = pathParts.length > 0 ? pathParts[0] : ""; - - // Find the nav section that matches the current page's top-level folder - // e.g., for "/getting-started/back-end/setup", find the "Getting Started" section - const matchingSection = navSections?.find(section => { - // Normalize section title to match URL segment - // e.g., "Getting Started" -> "getting-started" - const normalizedTitle = section.title.toLowerCase().replace(/\s+/g, '-'); - return normalizedTitle === firstSegment; - }); - - // If no matching section found, fall back to empty array - const navItems = matchingSection?.items || []; - - return ( - - ); - } - - return ( - - ); -} +import { PageAsset } from "@/components/page-asset"; +import { ErrorPage } from "@/components/error"; +import { getDotCMSPage } from "@/util/getDotCMSPage"; +import { getNavSections } from "@/services/docs/getNavSections"; +import { getSideNav } from "@/services/docs/getSideNav"; +import { BlockPageAsset } from "@/components/page-asset-with-content-block"; +/** + * Generate metadata + * + * @export + * @param {*} { params, searchParams } + * @return {*} + */ +export async function generateMetadata({ params }) { + const finalParams = await params; + const path = finalParams?.slug?.join("/") || "/"; + + try { + const pageContent = await getDotCMSPage(path); + if (!pageContent) { + return { + title: "not found", + }; + } + + const { pageAsset } = pageContent; + const page = pageAsset.page; + + const title = page?.friendlyName || page?.title; + + const description = page?.description || page?.teaser || page?.seoDescription || "dotCMS Dev Site, Documentation and Resources. Learn how to build with dotCMS"; + const hostname = "https://dev.dotcms.com"; + const keywords = page?.tags ? page?.tags.join(", ") : "dotcms, dotcms documentation, learn dotcms, dotcms api, dotcms dev, dotcms developer, dotcms developer documentation, dotcms developer api, dotcms developer documentation, dotcms developer api"; + + return { + title: title, + description: description, + url: `${hostname}${path}`, + siteName: 'dotCMS Docs', + keywords: keywords, + alternates: { + canonical: `${hostname}${path}`, + }, + metadataBase: new URL(hostname), + images: [{ + url: `${hostname}/dA/4b13a794db115b14ce79d30850712188/1024maxw/80q/}`, + width: 1200, + height: 630, + alt: description || title, + }], + locale: 'en_US', + type: 'article', + }; + } catch (e) { + console.error('Error generating metadata:', e?.message || e); + return { + title: "not found", + }; + } +} + +export default async function Page({ params }) { + const finalParams = await params; + + const path = finalParams?.slug?.join("/") || "/"; + const pageContent = await getDotCMSPage(path); + + + if (!pageContent) { + return ; + } + + const { pageAsset } = pageContent; + const isBlockPage = pageAsset?.page?.contentType === "BlockPage" + + if (isBlockPage) { + // Fetch navigation data (reuse cached nav sections instead of separate API call) + const [searchData, navSections] = await Promise.all([ + getSideNav(), + getNavSections({ path: '/docs/nav', depth: 4, languageId: 1, ttlSeconds: 600 }) + ]); + + // Extract the first segment of the URL to find the matching nav section + const pathParts = pageAsset?.page?.url.split("/").filter(part => part.length > 0); + const firstSegment = pathParts.length > 0 ? pathParts[0] : ""; + + // Find the nav section that matches the current page's top-level folder + // e.g., for "/getting-started/back-end/setup", find the "Getting Started" section + const matchingSection = navSections?.find(section => { + // Normalize section title to match URL segment + // e.g., "Getting Started" -> "getting-started" + const normalizedTitle = section.title.toLowerCase().replace(/\s+/g, '-'); + return normalizedTitle === firstSegment; + }); + + // If no matching section found, fall back to empty array + const navItems = matchingSection?.items || []; + + return ( + + ); + } + + return ( + + ); +} diff --git a/app/api/sitemap/route.js b/app/api/sitemap/route.js index 9949382..be61585 100644 --- a/app/api/sitemap/route.js +++ b/app/api/sitemap/route.js @@ -1,190 +1,190 @@ -import { getSideNav } from "@/services/docs/getSideNav"; -import { Config } from "@/util/config"; -import { getBlogListing } from "@/services/blog/getBlogListing"; -import { getDevResources } from "@/services/learning/getDevResources"; -const extractHrefs = (obj) => { - const baseURL = `${Config.CDNHost}/docs/`; - let hrefs = []; - - if (obj && typeof obj === "object") { - if (obj?.urlTitle) { - const href = `${baseURL}${obj?.urlTitle}`; - hrefs.push({ href, modDate: obj?.modDate }); - } - - for (const key in obj) { - if (obj.hasOwnProperty(key)) { - const value = obj[key]; - if (Array.isArray(value)) { - for (const item of value) { - hrefs = hrefs.concat(extractHrefs(item)); - } - } else if (typeof value === "object") { - hrefs = hrefs.concat(extractHrefs(value)); - } - } - } - } - - return hrefs; -}; - -const getBlogs = async () => { - var finalBlogs = []; - - const { blogs, pagination } = await getBlogListing({ - tagFilter: "", - page: 1, - pageSize: 50, - }); - blogs.map((blog) => { - finalBlogs.push({ - href: `${Config.CDNHost}/blog/${blog.urlTitle}`, - modDate: blog.modDate, - }); - }); - for (var j = 2; j <= pagination.totalPages; j++) { - const { blogs } = await getBlogListing({ - tagFilter: "", - page: j, - pageSize: 50, - }); - if (blogs.length == 0) { - break; - } - blogs.map((blog) => { - finalBlogs.push({ - href: `${Config.CDNHost}/blog/${blog.urlTitle}`, - modDate: blog.modDate, - }); - }); - } - - return finalBlogs; -}; - -const loadDevResources = async () => { - var finalDevResources = []; - - const { devResources, pagination } = await getDevResources({ - page: 1, - pageSize: 50, - }); - devResources.map((devResource) => { - finalDevResources.push({ - href: `${Config.CDNHost}/learning/${devResource.slug}`, - modDate: devResource.modDate, - }); - }); - for (var j = 2; j <= pagination.totalPages; j++) { - const { devResources } = await getDevResources({ - page: j, - pageSize: 50, - }); - if (devResources.length == 0) { - break; - } - devResources.map((blog) => { - finalDevResources.push({ - href: `${Config.CDNHost}/learning/${blog.slug}`, - modDate: blog.modDate, - }); - }); - } - - return finalDevResources; -}; - -const createSitemap = (links, blogs, devResources) => { - const urls = (links || []) - .map( - ({ href, modDate }) => ` - - ${href} - ${ - modDate - ? new Date(modDate).toISOString().split("T")[0] - : new Date().toISOString().split("T")[0] - } - monthly - 0.8 - - ` - ) - .join(""); - - const blogLinks = (blogs || []) - .map( - ({ href, modDate }) => ` - - ${href} - ${ - modDate - ? new Date(modDate).toISOString().split("T")[0] - : new Date().toISOString().split("T")[0] - } - monthly - 0.8 - - ` - ) - .join(""); - - const devResourceLinks = (devResources || []) - .map( - ({ href, modDate }) => ` - - ${href} - ${ - modDate - ? new Date(modDate).toISOString().split("T")[0] - : new Date().toISOString().split("T")[0] - } - monthly - 0.8 - - ` - ) - .join(""); - - return ` - - ${devResourceLinks} - ${blogLinks} - ${urls} - - `; -}; - -export async function GET() { - try { - - console.log("Config.CDNHost", Config.CDNHost); - const docs = await getSideNav(); - const blogs = await getBlogs(); - const devResources = await loadDevResources(); - if (!docs || typeof docs !== "object") { - throw new Error("Invalid docs format returned from getCoreNavLeft()"); - } - - const links = extractHrefs(docs); - const sitemap = createSitemap(links, blogs, devResources); - - return new Response(sitemap, { - headers: { - "Content-Type": "application/xml", - }, - }); - } catch (error) { - console.error("Error generating sitemap:", error); - return new Response( - JSON.stringify({ message: "Failed to generate sitemap", error }), - { - status: 500, - headers: { - "Content-Type": "application/json", - }, - } - ); - } -} +import { getSideNav } from "@/services/docs/getSideNav"; +import { Config } from "@/util/config"; +import { getBlogListing } from "@/services/blog/getBlogListing"; +import { getDevResources } from "@/services/learning/getDevResources"; +const extractHrefs = (obj) => { + const baseURL = `${Config.CDNHost}/docs/`; + let hrefs = []; + + if (obj && typeof obj === "object") { + if (obj?.urlTitle) { + const href = `${baseURL}${obj?.urlTitle}`; + hrefs.push({ href, modDate: obj?.modDate }); + } + + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + const value = obj[key]; + if (Array.isArray(value)) { + for (const item of value) { + hrefs = hrefs.concat(extractHrefs(item)); + } + } else if (typeof value === "object") { + hrefs = hrefs.concat(extractHrefs(value)); + } + } + } + } + + return hrefs; +}; + +const getBlogs = async () => { + var finalBlogs = []; + + const { blogs, pagination } = await getBlogListing({ + tagFilter: "", + page: 1, + pageSize: 50, + }); + blogs.map((blog) => { + finalBlogs.push({ + href: `${Config.CDNHost}/blog/${blog.urlTitle}`, + modDate: blog.modDate, + }); + }); + for (var j = 2; j <= pagination.totalPages; j++) { + const { blogs } = await getBlogListing({ + tagFilter: "", + page: j, + pageSize: 50, + }); + if (blogs.length == 0) { + break; + } + blogs.map((blog) => { + finalBlogs.push({ + href: `${Config.CDNHost}/blog/${blog.urlTitle}`, + modDate: blog.modDate, + }); + }); + } + + return finalBlogs; +}; + +const loadDevResources = async () => { + var finalDevResources = []; + + const { devResources, pagination } = await getDevResources({ + page: 1, + pageSize: 50, + }); + devResources.map((devResource) => { + finalDevResources.push({ + href: `${Config.CDNHost}/learning/${devResource.slug}`, + modDate: devResource.modDate, + }); + }); + for (var j = 2; j <= pagination.totalPages; j++) { + const { devResources } = await getDevResources({ + page: j, + pageSize: 50, + }); + if (devResources.length == 0) { + break; + } + devResources.map((blog) => { + finalDevResources.push({ + href: `${Config.CDNHost}/learning/${blog.slug}`, + modDate: blog.modDate, + }); + }); + } + + return finalDevResources; +}; + +const createSitemap = (links, blogs, devResources) => { + const urls = (links || []) + .map( + ({ href, modDate }) => ` + + ${href} + ${ + modDate + ? new Date(modDate).toISOString().split("T")[0] + : new Date().toISOString().split("T")[0] + } + monthly + 0.8 + + ` + ) + .join(""); + + const blogLinks = (blogs || []) + .map( + ({ href, modDate }) => ` + + ${href} + ${ + modDate + ? new Date(modDate).toISOString().split("T")[0] + : new Date().toISOString().split("T")[0] + } + monthly + 0.8 + + ` + ) + .join(""); + + const devResourceLinks = (devResources || []) + .map( + ({ href, modDate }) => ` + + ${href} + ${ + modDate + ? new Date(modDate).toISOString().split("T")[0] + : new Date().toISOString().split("T")[0] + } + monthly + 0.8 + + ` + ) + .join(""); + + return ` + + ${devResourceLinks} + ${blogLinks} + ${urls} + + `; +}; + +export async function GET() { + try { + + console.log("Config.CDNHost", Config.CDNHost); + const docs = await getSideNav(); + const blogs = await getBlogs(); + const devResources = await loadDevResources(); + if (!docs || typeof docs !== "object") { + throw new Error("Invalid docs format returned from getCoreNavLeft()"); + } + + const links = extractHrefs(docs); + const sitemap = createSitemap(links, blogs, devResources); + + return new Response(sitemap, { + headers: { + "Content-Type": "application/xml", + }, + }); + } catch (error) { + console.error("Error generating sitemap:", error); + return new Response( + JSON.stringify({ message: "Failed to generate sitemap", error }), + { + status: 500, + headers: { + "Content-Type": "application/json", + }, + } + ); + } +} diff --git a/app/blog/[slug]/page.js b/app/blog/[slug]/page.js index 1f66302..397adec 100644 --- a/app/blog/[slug]/page.js +++ b/app/blog/[slug]/page.js @@ -1,180 +1,180 @@ -import { notFound } from "next/navigation"; -import Script from "next/script"; -import Header from "@/components/header/header"; -import Footer from "@/components/footer"; -import { getBlogDetailQuery } from "@/services/blog/getBlogDetail"; -import BlogDetailComponent from "@/components/blogs/blog-detail"; -import { ErrorPage } from "@/components/error"; -import { extractAssetId } from "@/util/utils"; - - -export async function generateMetadata({ params }) { - const finalParams = await params; - const slug = finalParams.slug - - if(!slug) { - return notFound(); - } - const post = await getBlogDetailQuery(slug); - - if (!post) { - return { - title: 'Blog Post Not Found', - }; - } - - // Check if the post's tags include 'dot:meta-no-index' - const tags = post.tags || []; - const shouldNoIndex = Array.isArray(tags) - ? tags.includes('dot:meta-no-index') - : typeof tags === 'string' && tags.includes('dot:meta-no-index'); - - const blogHostName = post.host?.hostName || 'dev.dotcms.com'; - - const hostname = (blogHostName === 'dotcms.com') - ? 'https://www.dotcms.com' - : `https://dev.dotcms.com` - - - const imageUrl = post.image?.fileAsset?.idPath - ? `${hostname}/dA/${extractAssetId(post.image.fileAsset.idPath)}/70q/1000maxw/${post.inode}` - : `${hostname}/images/default-blog-image.jpg`; - - const metadata = { - alternates: { - canonical: `${hostname}/blog/${post.urlTitle}`, - }, - title: post.title, - description: post.teaser || `Read ${post.title} on dotCMS Developer Blog`, - canonical: `${hostname}/blog/${post.urlTitle}`, - metadataBase: new URL(hostname), - - // OpenGraph - openGraph: { - title: post.title, - description: post.teaser, - url: `${hostname}/blog/${post.urlTitle}`, - siteName: 'dotCMS Developer Blog', - images: [{ - url: imageUrl, - width: 1200, - height: 630, - alt: post.image?.description || post.title, - }], - locale: 'en_US', - type: 'article', - }, - - // Twitter - twitter: { - card: 'summary_large_image', - title: post.title, - description: post.teaser, - images: [imageUrl], - creator: '@dotcms', - site: '@dotcms', - }, - - // Article specific - article: { - publishedTime: post.publishDate, - modifiedTime: post.modDate, - authors: post.author ? [`${post.author.firstName} ${post.author.lastName}`] : ['dotCMS Team'], - tags: post.tags, - }, - - // Other meta tags - keywords: post.tags?.join(', '), - robots: shouldNoIndex - ? 'noindex, nofollow' - : { - index: true, - follow: true, - }, - }; - - return metadata; -} - - - -// JSON-LD component for blog posts -function JsonLd({ post, hostname }) { - const authorName = post.author - ? `${post.author.firstName} ${post.author.lastName}` - : 'dotCMS Team'; - - const imageUrl = post.image?.fileAsset?.idPath - ? `${hostname}/dA/${extractAssetId(post.image.fileAsset.idPath)}/70q/1000maxw/${post.inode}` - : `${hostname}/images/default-blog-image.jpg`; - - const jsonLd = { - "@context": "https://schema.org", - "@type": "BlogPosting", - "headline": post.title, - "description": post.teaser, - "image": imageUrl, - "datePublished": post.publishDate, - "dateModified": post.modDate, - "author": { - "@type": "Person", - "name": authorName - }, - "publisher": { - "@type": "Organization", - "name": "dotCMS", - "logo": { - "@type": "ImageObject", - "url": `${hostname}/images/dotcms-logo.png` - } - }, - "mainEntityOfPage": { - "@type": "WebPage", - "@id": `${hostname}/blog/${post.urlTitle}` - }, - "keywords": post.tags?.join(', '), - "articleBody": post.body - }; - - return ( - - ); -} +import Script from 'next/script'; + +export function LeadboxerScript() { + return ( + + ); +} diff --git a/components/metrics/MicrosoftClarity.js b/components/metrics/MicrosoftClarity.js index 1b1a868..34ba63f 100644 --- a/components/metrics/MicrosoftClarity.js +++ b/components/metrics/MicrosoftClarity.js @@ -1,22 +1,22 @@ -import Script from "next/script" - -const MicrosoftClarity = () => { - return ( -