Skip to content

Commit 2e781bc

Browse files
optimizing the performance of the application and integrated vercel speed insights
1 parent d022083 commit 2e781bc

20 files changed

Lines changed: 277 additions & 153 deletions

File tree

app/api/docs/[id]/pages/[pageId]/route.ts

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
22
import { updatePage } from '@/lib/db';
33
import { getCurrentUser } from '@/lib/users';
44
import type { DocumentSection } from '@/lib/docs';
5+
import { revalidateDocsNavData, revalidatePageCaches } from '@/lib/revalidate-docs-cache';
6+
7+
const debugSave = process.env.DEBUG_API_SAVE === '1';
58

69
// Tiptap/ProseMirror JSON node types for type-safe HTML rendering
710
interface TiptapMark {
@@ -35,16 +38,17 @@ function renderNodeToHTML(node: TiptapNode): string {
3538
};
3639

3740
const renderMarks = (text: string, marks: TiptapMark[] = []) => {
38-
// DEBUG: Log original text
39-
console.log('🟡 [DEBUG renderMarks] Original text:', {
40-
text: JSON.stringify(text),
41-
textLength: text.length,
42-
spaceCount: (text.match(/ /g) || []).length,
43-
nbspCount: (text.match(/\u00A0/g) || []).length,
44-
hasMultipleSpaces: / {2,}/.test(text),
45-
hasNbsp: text.includes('\u00A0')
46-
});
47-
41+
if (debugSave) {
42+
console.log('[DEBUG renderMarks] Original text:', {
43+
text: JSON.stringify(text),
44+
textLength: text.length,
45+
spaceCount: (text.match(/ /g) || []).length,
46+
nbspCount: (text.match(/\u00A0/g) || []).length,
47+
hasMultipleSpaces: / {2,}/.test(text),
48+
hasNbsp: text.includes('\u00A0'),
49+
});
50+
}
51+
4852
// First escape the text (this will escape & but not \u00A0)
4953
let result = escapeHTML(text);
5054

@@ -56,19 +60,22 @@ function renderNodeToHTML(node: TiptapNode): string {
5660
// This is a fallback in case non-breaking spaces weren't inserted
5761
const beforeReplace = result;
5862
result = result.replace(/ {2,}/g, (match) => {
59-
// Convert all spaces in the sequence to  
6063
const replacement = ' '.repeat(match.length);
61-
console.log('🟡 [DEBUG renderMarks] Replacing', match.length, 'spaces with', replacement.length, '  entities');
64+
if (debugSave) {
65+
console.log('[DEBUG renderMarks] Replacing spaces', match.length, replacement.length);
66+
}
6267
return replacement;
6368
});
64-
65-
// DEBUG: Log after processing
66-
if (beforeReplace !== result || text.includes('\u00A0')) {
67-
console.log('🟡 [DEBUG renderMarks] After processing:', {
69+
70+
if (
71+
debugSave &&
72+
(beforeReplace !== result || text.includes('\u00A0'))
73+
) {
74+
console.log('[DEBUG renderMarks] After processing:', {
6875
original: JSON.stringify(text),
6976
result: JSON.stringify(result),
7077
containsNbsp: result.includes(' '),
71-
nbspCount: (result.match(/ /g) || []).length
78+
nbspCount: (result.match(/ /g) || []).length,
7279
});
7380
}
7481

@@ -113,14 +120,15 @@ function renderNodeToHTML(node: TiptapNode): string {
113120

114121
switch (node.type) {
115122
case 'text':
116-
// DEBUG: Log ALL text nodes to see what we're working with
117-
console.log('🟡 [DEBUG renderNodeToHTML] Text node:', {
118-
text: JSON.stringify(node.text),
119-
textLength: node.text?.length || 0,
120-
spaceCount: node.text ? (node.text.match(/ /g) || []).length : 0,
121-
hasMultipleSpaces: node.text ? / {2,}/.test(node.text) : false,
122-
multipleSpaceSequences: node.text ? node.text.match(/ {2,}/g) : null
123-
});
123+
if (debugSave) {
124+
console.log('[DEBUG renderNodeToHTML] Text node:', {
125+
text: JSON.stringify(node.text),
126+
textLength: node.text?.length || 0,
127+
spaceCount: node.text ? (node.text.match(/ /g) || []).length : 0,
128+
hasMultipleSpaces: node.text ? / {2,}/.test(node.text) : false,
129+
multipleSpaceSequences: node.text ? node.text.match(/ {2,}/g) : null,
130+
});
131+
}
124132
return renderMarks(node.text || '', node.marks);
125133

126134
case 'paragraph':
@@ -339,14 +347,17 @@ function convertTiptapJSONToSections(tiptapJSON: TiptapJSON, pageId: string) {
339347
// Add any content (paragraphs, lists, code blocks, etc.) as HTML
340348
const html = renderNodeToHTML(node);
341349

342-
// DEBUG: Log HTML output for paragraphs with multiple spaces
343-
if (node.type === 'paragraph' && html.includes(' ') || html.includes(' ')) {
350+
if (
351+
debugSave &&
352+
node.type === 'paragraph' &&
353+
(html.includes(' ') || html.includes(' '))
354+
) {
344355
console.log('[DEBUG convertTiptapJSONToSections] Paragraph HTML:', {
345356
nodeType: node.type,
346-
html: html.substring(0, 200), // First 200 chars
357+
html: html.substring(0, 200),
347358
containsMultipleSpaces: / {2,}/.test(html),
348359
containsNbsp: html.includes(' '),
349-
htmlLength: html.length
360+
htmlLength: html.length,
350361
});
351362
}
352363

@@ -513,13 +524,14 @@ export async function PATCH(
513524
const body = await request.json();
514525
const { content, projectId } = body;
515526

516-
// DEBUG: Log what the server receives
517-
console.log('🟡 [DEBUG API PATCH] Received save request:', {
518-
docId,
519-
pageId,
520-
projectId,
521-
contentPreview: JSON.stringify(content).substring(0, 500)
522-
});
527+
if (debugSave) {
528+
console.log('[DEBUG API PATCH] save request', {
529+
docId,
530+
pageId,
531+
projectId,
532+
contentPreview: JSON.stringify(content).substring(0, 500),
533+
});
534+
}
523535

524536
if (!content) {
525537
return NextResponse.json(
@@ -528,21 +540,24 @@ export async function PATCH(
528540
);
529541
}
530542

531-
// DEBUG: Log full content structure before conversion
532-
console.log('🟡 [DEBUG API PATCH] Full content structure:', JSON.stringify(content, null, 2));
533-
543+
if (debugSave) {
544+
console.log('[DEBUG API PATCH] full content', JSON.stringify(content, null, 2));
545+
}
546+
534547
// Convert Tiptap JSON to sections and extract title
535548
const { title, sections } = convertTiptapJSONToSections(content, pageId);
536549

537550
// Extract headings for table of contents
538551
const toc = extractHeadingsForTOC(content);
539552

540-
// DEBUG: Log sections after conversion
541-
console.log('🟡 [DEBUG API PATCH] Converted sections:', JSON.stringify(sections, null, 2));
542-
console.log('🟡 [DEBUG API PATCH] Extracted TOC:', JSON.stringify(toc, null, 2));
553+
if (debugSave) {
554+
console.log('[DEBUG API PATCH] sections', JSON.stringify(sections, null, 2), 'toc', JSON.stringify(toc));
555+
}
543556

544557
// Update or create page using updatePage function (it will verify document ownership)
545558
const page = await updatePage(docId, pageId, title, sections as DocumentSection[], user.id, projectId);
559+
revalidatePageCaches(pageId);
560+
revalidateDocsNavData();
546561

547562
return NextResponse.json({
548563
success: true,

app/api/docs/[id]/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
22
import { revalidatePath } from 'next/cache';
33
import { updateDocument, deleteDocument } from '@/lib/db';
44
import { getCurrentUser } from '@/lib/users';
5+
import { revalidateDocsNavData } from '@/lib/revalidate-docs-cache';
56

67
export async function PATCH(
78
request: NextRequest,
@@ -31,6 +32,7 @@ export async function PATCH(
3132

3233
// Update document (updateDocument will verify ownership)
3334
const doc = await updateDocument(docId, name, user.id, projectId);
35+
revalidateDocsNavData();
3436

3537
const newHref = projectId
3638
? `/docs/projects/${projectId}/${doc.id}`
@@ -71,6 +73,7 @@ export async function DELETE(
7173

7274
// Delete document (deleteDocument will verify ownership)
7375
await deleteDocument(docId, user.id, projectId);
76+
revalidateDocsNavData();
7477

7578
// Revalidate the project page and docs pages to clear cache
7679
if (projectId) {

app/api/docs/[id]/sections/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { addPageToDocument, prisma } from '@/lib/db';
33
import { getCurrentUser } from '@/lib/users';
4+
import { revalidateDocsNavData } from '@/lib/revalidate-docs-cache';
45

56
export async function POST(
67
request: NextRequest,
@@ -39,6 +40,7 @@ export async function POST(
3940
Array.isArray(sectionContent) ? sectionContent : [sectionContent],
4041
projectId
4142
);
43+
revalidateDocsNavData();
4244

4345
// Get updated document by UUID
4446
const updatedDoc = await prisma.document.findUnique({

app/api/docs/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { createDocument } from '@/lib/db';
33
import { getCurrentUser } from '@/lib/users';
4+
import { revalidateDocsNavData } from '@/lib/revalidate-docs-cache';
45

56
export async function POST(request: NextRequest) {
67
try {
@@ -26,6 +27,7 @@ export async function POST(request: NextRequest) {
2627
// Create new document (createDocument will verify project ownership if projectId is provided)
2728
if (projectId) {
2829
const newDoc = await createDocument(name, description, user.id, projectId);
30+
revalidateDocsNavData();
2931

3032
// Get the first page slug from the created document
3133
const firstPage = newDoc.content.pages[0];
@@ -39,6 +41,7 @@ export async function POST(request: NextRequest) {
3941
} else {
4042
// Create document in "Your Docs" (createDocument handles unique ID generation)
4143
const newDoc = await createDocument(name, description, user.id);
44+
revalidateDocsNavData();
4245

4346
// Get the first page slug from the created document
4447
const firstPage = newDoc.content.pages[0];

app/api/documents/[id]/publish/route.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { getCurrentUser } from '@/lib/users';
33
import { prisma } from '@/lib/db';
4+
import {
5+
revalidateDocsNavData,
6+
revalidatePublishedNavData,
7+
} from '@/lib/revalidate-docs-cache';
48
import {
59
validateDocumentForPublishing,
610
generatePublishSlug,
@@ -137,6 +141,8 @@ export async function POST(
137141
publishSlug,
138142
},
139143
});
144+
revalidatePublishedNavData();
145+
revalidateDocsNavData();
140146

141147
return NextResponse.json({
142148
success: true,
@@ -195,6 +201,8 @@ export async function DELETE(
195201
await (prisma.publishedDocument.deleteMany as any)({
196202
where: { documentId },
197203
});
204+
revalidatePublishedNavData();
205+
revalidateDocsNavData();
198206

199207
return NextResponse.json({
200208
success: true,

app/api/projects/[id]/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { updateProject, deleteProject } from '@/lib/db';
33
import { getCurrentUser } from '@/lib/users';
4+
import { revalidateDocsNavData } from '@/lib/revalidate-docs-cache';
45

56
export async function PATCH(
67
request: NextRequest,
@@ -30,6 +31,7 @@ export async function PATCH(
3031

3132
// Update project (updateProject will verify ownership)
3233
const project = await updateProject(projectId, name, user.id);
34+
revalidateDocsNavData();
3335

3436
return NextResponse.json({
3537
success: true,
@@ -63,6 +65,7 @@ export async function DELETE(
6365

6466
// Delete project (deleteProject will verify ownership)
6567
await deleteProject(projectId, user.id);
68+
revalidateDocsNavData();
6669

6770
return NextResponse.json({
6871
success: true,

app/api/projects/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { createProject } from '@/lib/db';
33
import { getCurrentUser } from '@/lib/users';
4+
import { revalidateDocsNavData } from '@/lib/revalidate-docs-cache';
45

56
export async function POST(request: NextRequest) {
67
try {
@@ -25,6 +26,7 @@ export async function POST(request: NextRequest) {
2526

2627
// Create new project (createProject handles unique ID generation)
2728
const newProject = await createProject(name, description, user.id);
29+
revalidateDocsNavData();
2830

2931
return NextResponse.json({
3032
success: true,

app/docs/[[...slug]]/layout.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
buildSidebarItems,
66
type ProcessedYourDoc,
77
} from '@/lib/docs';
8-
import { getAllDocsNavDataCached, getAllPublishedDocsNavCached } from '@/lib/db';
8+
import { getDocsNavBundleForUser } from '@/lib/db';
99
import { getCurrentUser } from '@/lib/users';
1010
import { DocsLayoutClient } from '@/components/docs/DocsLayoutClient';
1111
import { redirect } from 'next/navigation';
@@ -21,21 +21,19 @@ export default async function DocsLayout({
2121
redirect('/sign-in');
2222
}
2323

24-
// Use CACHED nav data so we don't run 4–6 DB queries on every navigation
25-
const data = await getAllDocsNavDataCached(user.id);
24+
// Same bundle as page.tsx — React cache() dedupes so we only hit DB/cache once per request
25+
const { data, publishedDocsData } = await getDocsNavBundleForUser(user.id);
2626
const processedProjects = processProjects(data.projects);
2727
const processedYourDocs = processYourDocs(data.yourDocs);
2828

2929
let processedPublishedDocs: ProcessedYourDoc[] = [];
3030
try {
31-
const publishedDocsData = await getAllPublishedDocsNavCached();
3231
const publishSlugsMap = new Map(Object.entries(publishedDocsData.publishSlugs));
3332
processedPublishedDocs = processPublishedDocs(
3433
publishedDocsData.documents,
3534
publishSlugsMap
3635
);
3736
} catch (error) {
38-
// If published docs can't be fetched (e.g., schema not migrated), just continue without them
3937
console.error('Error fetching published docs:', error);
4038
processedPublishedDocs = [];
4139
}

0 commit comments

Comments
 (0)