@@ -11,14 +11,24 @@ import {Home} from 'sentry-docs/components/home';
1111import { Include } from 'sentry-docs/components/include' ;
1212import { PlatformContent } from 'sentry-docs/components/platformContent' ;
1313import {
14+ DocNode ,
1415 getCurrentPlatformOrGuide ,
1516 getDocsRootNode ,
17+ getNextNode ,
18+ getPreviousNode ,
1619 nodeForPath ,
1720} from 'sentry-docs/docTree' ;
1821import { isDeveloperDocs } from 'sentry-docs/isDeveloperDocs' ;
19- import { getDevDocsFrontMatter , getDocsFrontMatter , getFileBySlug } from 'sentry-docs/mdx' ;
22+ import {
23+ getDevDocsFrontMatter ,
24+ getDocsFrontMatter ,
25+ getFileBySlug ,
26+ getVersionsFromDoc ,
27+ } from 'sentry-docs/mdx' ;
2028import { mdxComponents } from 'sentry-docs/mdxComponents' ;
2129import { setServerContext } from 'sentry-docs/serverContext' ;
30+ import { PaginationNavNode } from 'sentry-docs/types/paginationNavNode' ;
31+ import { stripVersion } from 'sentry-docs/versioning' ;
2232
2333export async function generateStaticParams ( ) {
2434 const docs = await ( isDeveloperDocs ? getDevDocsFrontMatter ( ) : getDocsFrontMatter ( ) ) ;
@@ -36,7 +46,11 @@ export const dynamic = 'force-static';
3646
3747const mdxComponentsWithWrapper = mdxComponents (
3848 { Include, PlatformContent} ,
39- ( { children, frontMatter} ) => < DocPage frontMatter = { frontMatter } > { children } </ DocPage >
49+ ( { children, frontMatter, nextPage, previousPage} ) => (
50+ < DocPage frontMatter = { frontMatter } nextPage = { nextPage } previousPage = { previousPage } >
51+ { children }
52+ </ DocPage >
53+ )
4054) ;
4155
4256function MDXLayoutRenderer ( { mdxSource, ...rest } ) {
@@ -47,11 +61,48 @@ function MDXLayoutRenderer({mdxSource, ...rest}) {
4761export default async function Page ( { params} : { params : { path ?: string [ ] } } ) {
4862 // get frontmatter of all docs in tree
4963 const rootNode = await getDocsRootNode ( ) ;
64+
5065 setServerContext ( {
5166 rootNode,
5267 path : params . path ?? [ ] ,
5368 } ) ;
5469
70+ if ( ! params . path && ! isDeveloperDocs ) {
71+ return < Home /> ;
72+ }
73+
74+ const pageNode = nodeForPath ( rootNode , params . path ?? '' ) ;
75+
76+ if ( ! pageNode ) {
77+ // eslint-disable-next-line no-console
78+ console . warn ( 'no page node' , params . path ) ;
79+ return notFound ( ) ;
80+ }
81+
82+ // gather previous and next page that will be displayed in the bottom pagination
83+ const getPaginationDetails = (
84+ getNode : ( node : DocNode ) => DocNode | undefined | 'root' ,
85+ page : PaginationNavNode | undefined
86+ ) => {
87+ if ( page && 'path' in page && 'title' in page ) {
88+ return page ;
89+ }
90+
91+ const node = getNode ( pageNode ) ;
92+
93+ if ( node === 'root' ) {
94+ return { path : '' , title : 'Welcome to Sentry' } ;
95+ }
96+
97+ return node ? { path : node . path , title : node . frontmatter . title } : undefined ;
98+ } ;
99+
100+ const previousPage = getPaginationDetails (
101+ getPreviousNode ,
102+ pageNode ?. frontmatter ?. previousPage
103+ ) ;
104+ const nextPage = getPaginationDetails ( getNextNode , pageNode ?. frontmatter ?. nextPage ) ;
105+
55106 if ( isDeveloperDocs ) {
56107 // get the MDX for the current doc and render it
57108 let doc : Awaited < ReturnType < typeof getFileBySlug > > | null = null ;
@@ -67,13 +118,17 @@ export default async function Page({params}: {params: {path?: string[]}}) {
67118 }
68119 const { mdxSource, frontMatter} = doc ;
69120 // pass frontmatter tree into sidebar, rendered page + fm into middle, headers into toc
70- return < MDXLayoutRenderer mdxSource = { mdxSource } frontMatter = { frontMatter } /> ;
71- }
72- if ( ! params . path ) {
73- return < Home /> ;
121+ return (
122+ < MDXLayoutRenderer
123+ mdxSource = { mdxSource }
124+ frontMatter = { frontMatter }
125+ nextPage = { nextPage }
126+ previousPage = { previousPage }
127+ />
128+ ) ;
74129 }
75130
76- if ( params . path [ 0 ] === 'api' && params . path . length > 1 ) {
131+ if ( params . path ?. [ 0 ] === 'api' && params . path . length > 1 ) {
77132 const categories = await apiCategories ( ) ;
78133 const category = categories . find ( c => c . slug === params ?. path ?. [ 1 ] ) ;
79134 if ( category ) {
@@ -87,13 +142,6 @@ export default async function Page({params}: {params: {path?: string[]}}) {
87142 }
88143 }
89144
90- const pageNode = nodeForPath ( rootNode , params . path ) ;
91- if ( ! pageNode ) {
92- // eslint-disable-next-line no-console
93- console . warn ( 'no page node' , params . path ) ;
94- return notFound ( ) ;
95- }
96-
97145 // get the MDX for the current doc and render it
98146 let doc : Awaited < ReturnType < typeof getFileBySlug > > | null = null ;
99147 try {
@@ -108,8 +156,19 @@ export default async function Page({params}: {params: {path?: string[]}}) {
108156 }
109157 const { mdxSource, frontMatter} = doc ;
110158
159+ // collect versioned files
160+ const allFm = await getDocsFrontMatter ( ) ;
161+ const versions = getVersionsFromDoc ( allFm , pageNode . path ) ;
162+
111163 // pass frontmatter tree into sidebar, rendered page + fm into middle, headers into toc.
112- return < MDXLayoutRenderer mdxSource = { mdxSource } frontMatter = { frontMatter } /> ;
164+ return (
165+ < MDXLayoutRenderer
166+ mdxSource = { mdxSource }
167+ frontMatter = { { ...frontMatter , versions} }
168+ nextPage = { nextPage }
169+ previousPage = { previousPage }
170+ />
171+ ) ;
113172}
114173
115174type MetadataProps = {
@@ -118,6 +177,17 @@ type MetadataProps = {
118177 } ;
119178} ;
120179
180+ // Helper function to clean up canonical tags missing leading or trailing slash
181+ function formatCanonicalTag ( tag : string ) {
182+ if ( tag . charAt ( 0 ) !== '/' ) {
183+ tag = '/' + tag ;
184+ }
185+ if ( tag . charAt ( tag . length - 1 ) !== '/' ) {
186+ tag = tag + '/' ;
187+ }
188+ return tag ;
189+ }
190+
121191export async function generateMetadata ( { params} : MetadataProps ) : Promise < Metadata > {
122192 const domain = isDeveloperDocs
123193 ? 'https://develop.sentry.dev'
@@ -128,27 +198,37 @@ export async function generateMetadata({params}: MetadataProps): Promise<Metadat
128198 : domain ;
129199 let title =
130200 'Sentry Docs | Application Performance Monitoring & Error Tracking Software' ;
201+ let customCanonicalTag ;
131202 let description =
132203 'Self-hosted and cloud-based application performance monitoring & error tracking that helps software teams see clearer, solve quicker, & learn continuously.' ;
133204 const images = [ { url : `${ previewDomain ?? domain } /meta.jpg` , width : 1200 , height : 822 } ] ;
134205
135206 const rootNode = await getDocsRootNode ( ) ;
136207
137208 if ( params . path ) {
138- const pageNode = nodeForPath ( rootNode , params . path ) ;
209+ const pageNode = nodeForPath (
210+ rootNode ,
211+ stripVersion ( params . path . join ( '/' ) ) . split ( '/' )
212+ ) ;
139213 if ( pageNode ) {
140214 const guideOrPlatform = getCurrentPlatformOrGuide ( rootNode , params . path ) ;
215+
141216 title =
142217 pageNode . frontmatter . title +
143218 ( guideOrPlatform ? ` | Sentry for ${ guideOrPlatform . title } ` : '' ) ;
144219 description = pageNode . frontmatter . description ?? '' ;
220+
221+ if ( pageNode . frontmatter . customCanonicalTag ) {
222+ customCanonicalTag = formatCanonicalTag ( pageNode . frontmatter . customCanonicalTag ) ;
223+ }
145224 }
146225 }
147226
148- let canonical = domain ;
149- if ( params . path ) {
150- canonical = `${ domain } /${ params . path . join ( '/' ) } /` ;
151- }
227+ const canonical = customCanonicalTag
228+ ? domain + customCanonicalTag
229+ : params . path
230+ ? `${ domain } /${ params . path . join ( '/' ) } /`
231+ : domain ;
152232
153233 return {
154234 title,
0 commit comments