shipd 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/base-package/app/globals.css +126 -0
  2. package/base-package/app/layout.tsx +53 -0
  3. package/base-package/app/page.tsx +15 -0
  4. package/base-package/base.config.json +57 -0
  5. package/base-package/components/ui/avatar.tsx +53 -0
  6. package/base-package/components/ui/badge.tsx +46 -0
  7. package/base-package/components/ui/button.tsx +59 -0
  8. package/base-package/components/ui/card.tsx +92 -0
  9. package/base-package/components/ui/chart.tsx +353 -0
  10. package/base-package/components/ui/checkbox.tsx +32 -0
  11. package/base-package/components/ui/dialog.tsx +135 -0
  12. package/base-package/components/ui/dropdown-menu.tsx +257 -0
  13. package/base-package/components/ui/form.tsx +167 -0
  14. package/base-package/components/ui/input.tsx +21 -0
  15. package/base-package/components/ui/label.tsx +24 -0
  16. package/base-package/components/ui/progress.tsx +31 -0
  17. package/base-package/components/ui/resizable.tsx +56 -0
  18. package/base-package/components/ui/select.tsx +185 -0
  19. package/base-package/components/ui/separator.tsx +28 -0
  20. package/base-package/components/ui/sheet.tsx +139 -0
  21. package/base-package/components/ui/skeleton.tsx +13 -0
  22. package/base-package/components/ui/sonner.tsx +25 -0
  23. package/base-package/components/ui/switch.tsx +31 -0
  24. package/base-package/components/ui/tabs.tsx +66 -0
  25. package/base-package/components/ui/textarea.tsx +18 -0
  26. package/base-package/components/ui/toggle-group.tsx +73 -0
  27. package/base-package/components/ui/toggle.tsx +47 -0
  28. package/base-package/components/ui/tooltip.tsx +61 -0
  29. package/base-package/components.json +21 -0
  30. package/base-package/eslint.config.mjs +16 -0
  31. package/base-package/lib/utils.ts +6 -0
  32. package/base-package/middleware.ts +12 -0
  33. package/base-package/next.config.ts +27 -0
  34. package/base-package/package.json +49 -0
  35. package/base-package/postcss.config.mjs +5 -0
  36. package/base-package/public/favicon.svg +4 -0
  37. package/base-package/tailwind.config.ts +89 -0
  38. package/base-package/tsconfig.json +27 -0
  39. package/dist/index.js +1858 -956
  40. package/docs-template/README.md +74 -0
  41. package/features/ai-chat/README.md +316 -0
  42. package/features/ai-chat/app/api/chat/route.ts +16 -0
  43. package/features/ai-chat/app/dashboard/_components/chatbot.tsx +39 -0
  44. package/features/ai-chat/app/dashboard/chat/page.tsx +73 -0
  45. package/features/ai-chat/feature.config.json +22 -0
  46. package/features/analytics/README.md +364 -0
  47. package/features/analytics/feature.config.json +20 -0
  48. package/features/analytics/lib/posthog.ts +36 -0
  49. package/features/auth/README.md +409 -0
  50. package/features/auth/app/api/auth/[...all]/route.ts +4 -0
  51. package/features/auth/app/dashboard/layout.tsx +15 -0
  52. package/features/auth/app/dashboard/page.tsx +140 -0
  53. package/features/auth/app/sign-in/page.tsx +228 -0
  54. package/features/auth/app/sign-up/page.tsx +243 -0
  55. package/features/auth/auth-schema.ts +47 -0
  56. package/features/auth/components/auth/setup-instructions.tsx +123 -0
  57. package/features/auth/feature.config.json +33 -0
  58. package/features/auth/lib/auth-client.ts +8 -0
  59. package/features/auth/lib/auth.ts +295 -0
  60. package/features/auth/lib/email-stub.ts +55 -0
  61. package/features/auth/lib/email.ts +47 -0
  62. package/features/auth/middleware.patch.ts +43 -0
  63. package/features/database/README.md +312 -0
  64. package/features/database/db/drizzle.ts +48 -0
  65. package/features/database/db/schema.ts +21 -0
  66. package/features/database/drizzle.config.ts +13 -0
  67. package/features/database/feature.config.json +30 -0
  68. package/features/email/README.md +341 -0
  69. package/features/email/emails/components/layout.tsx +181 -0
  70. package/features/email/emails/password-reset.tsx +67 -0
  71. package/features/email/emails/payment-failed.tsx +167 -0
  72. package/features/email/emails/subscription-confirmation.tsx +129 -0
  73. package/features/email/emails/welcome.tsx +100 -0
  74. package/features/email/feature.config.json +22 -0
  75. package/features/email/lib/email.ts +118 -0
  76. package/features/file-upload/README.md +329 -0
  77. package/features/file-upload/app/api/upload-image/route.ts +64 -0
  78. package/features/file-upload/app/dashboard/upload/page.tsx +324 -0
  79. package/features/file-upload/feature.config.json +23 -0
  80. package/features/file-upload/lib/upload-image.ts +28 -0
  81. package/features/marketing-landing/README.md +333 -0
  82. package/features/marketing-landing/app/page.tsx +25 -0
  83. package/features/marketing-landing/components/homepage/cli-workflow-section.tsx +231 -0
  84. package/features/marketing-landing/components/homepage/features-section.tsx +152 -0
  85. package/features/marketing-landing/components/homepage/footer.tsx +53 -0
  86. package/features/marketing-landing/components/homepage/hero-section.tsx +112 -0
  87. package/features/marketing-landing/components/homepage/integrations.tsx +124 -0
  88. package/features/marketing-landing/components/homepage/navigation.tsx +116 -0
  89. package/features/marketing-landing/components/homepage/news-section.tsx +82 -0
  90. package/features/marketing-landing/components/homepage/pricing-section.tsx +98 -0
  91. package/features/marketing-landing/components/homepage/testimonials-section.tsx +34 -0
  92. package/features/marketing-landing/components/logos/BetterAuth.tsx +21 -0
  93. package/features/marketing-landing/components/logos/NeonPostgres.tsx +41 -0
  94. package/features/marketing-landing/components/logos/Nextjs.tsx +72 -0
  95. package/features/marketing-landing/components/logos/Polar.tsx +7 -0
  96. package/features/marketing-landing/components/logos/TailwindCSS.tsx +27 -0
  97. package/features/marketing-landing/components/logos/index.ts +6 -0
  98. package/features/marketing-landing/components/logos/shadcnui.tsx +8 -0
  99. package/features/marketing-landing/feature.config.json +23 -0
  100. package/features/payments/README.md +375 -0
  101. package/features/payments/app/api/subscription/route.ts +25 -0
  102. package/features/payments/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
  103. package/features/payments/app/dashboard/payment/page.tsx +126 -0
  104. package/features/payments/app/success/page.tsx +123 -0
  105. package/features/payments/feature.config.json +31 -0
  106. package/features/payments/lib/polar-products.ts +49 -0
  107. package/features/payments/lib/subscription.ts +148 -0
  108. package/features/payments/payments-schema.ts +30 -0
  109. package/features/seo/README.md +302 -0
  110. package/features/seo/app/blog/[slug]/page.tsx +314 -0
  111. package/features/seo/app/blog/page.tsx +107 -0
  112. package/features/seo/app/robots.txt +13 -0
  113. package/features/seo/app/sitemap.ts +70 -0
  114. package/features/seo/feature.config.json +19 -0
  115. package/features/seo/lib/seo-utils.ts +163 -0
  116. package/package.json +3 -1
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "payments",
3
+ "version": "1.0.0",
4
+ "description": "Polar.sh subscription management and payment processing",
5
+ "dependencies": {
6
+ "@polar-sh/sdk": "^0.42.1"
7
+ },
8
+ "devDependencies": {},
9
+ "envVars": [
10
+ "POLAR_ACCESS_TOKEN",
11
+ "POLAR_SUCCESS_URL",
12
+ "POLAR_WEBHOOK_SECRET",
13
+ "NEXT_PUBLIC_STARTER_TIER",
14
+ "NEXT_PUBLIC_STARTER_SLUG",
15
+ "NEXT_PUBLIC_APP_URL"
16
+ ],
17
+ "files": [
18
+ "app/dashboard/payment/**/*",
19
+ "app/success/**/*",
20
+ "app/api/subscription/**/*",
21
+ "lib/subscription.ts",
22
+ "lib/polar-products.ts",
23
+ "payments-schema.ts"
24
+ ],
25
+ "requires": [
26
+ "auth",
27
+ "database"
28
+ ],
29
+ "conflicts": []
30
+ }
31
+
@@ -0,0 +1,49 @@
1
+ import { Polar } from '@polar-sh/sdk';
2
+
3
+ const polarClient = new Polar({
4
+ accessToken: process.env.POLAR_ACCESS_TOKEN!,
5
+ });
6
+
7
+ export interface ProductDetails {
8
+ id: string;
9
+ name: string;
10
+ description: string | null;
11
+ prices: Array<{
12
+ id: string;
13
+ amount: number;
14
+ currency: string;
15
+ recurring_interval: 'month' | 'year';
16
+ }>;
17
+ }
18
+
19
+ /**
20
+ * Fetch product details from Polar
21
+ */
22
+ export async function getProductDetails(
23
+ productId: string
24
+ ): Promise<ProductDetails | null> {
25
+ try {
26
+ const product = await polarClient.products.get({
27
+ id: productId,
28
+ });
29
+
30
+ if (!product) {
31
+ return null;
32
+ }
33
+
34
+ return {
35
+ id: product.id,
36
+ name: product.name,
37
+ description: product.description || null,
38
+ prices: (product.prices || []).map((price) => ({
39
+ id: price.id,
40
+ amount: price.priceAmount || 0,
41
+ currency: price.priceCurrency || 'usd',
42
+ recurring_interval: (price.recurringInterval as 'month' | 'year') || 'month',
43
+ })),
44
+ };
45
+ } catch (error) {
46
+ console.error('Error fetching product from Polar:', error);
47
+ return null;
48
+ }
49
+ }
@@ -0,0 +1,148 @@
1
+ import { auth } from "@/lib/auth";
2
+ import { db } from "@/db/drizzle";
3
+ import { subscription } from "@/db/schema";
4
+ import { eq } from "drizzle-orm";
5
+ import { headers } from "next/headers";
6
+
7
+ export type SubscriptionDetails = {
8
+ id: string;
9
+ productId: string;
10
+ status: string;
11
+ amount: number;
12
+ currency: string;
13
+ recurringInterval: string;
14
+ currentPeriodStart: Date;
15
+ currentPeriodEnd: Date;
16
+ cancelAtPeriodEnd: boolean;
17
+ canceledAt: Date | null;
18
+ organizationId: string | null;
19
+ };
20
+
21
+ export type SubscriptionDetailsResult = {
22
+ hasSubscription: boolean;
23
+ subscription?: SubscriptionDetails;
24
+ error?: string;
25
+ errorType?: "CANCELED" | "EXPIRED" | "GENERAL";
26
+ };
27
+
28
+ export async function getSubscriptionDetails(): Promise<SubscriptionDetailsResult> {
29
+ try {
30
+ const session = await auth.api.getSession({
31
+ headers: await headers(),
32
+ });
33
+
34
+ if (!session?.user?.id) {
35
+ return { hasSubscription: false };
36
+ }
37
+
38
+ const userSubscriptions = await db
39
+ .select()
40
+ .from(subscription)
41
+ .where(eq(subscription.userId, session.user.id));
42
+
43
+ if (!userSubscriptions.length) {
44
+ return { hasSubscription: false };
45
+ }
46
+
47
+ // Get the most recent active subscription
48
+ const activeSubscription = userSubscriptions
49
+ .filter((sub) => sub.status === "active")
50
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0];
51
+
52
+ if (!activeSubscription) {
53
+ // Check for canceled or expired subscriptions
54
+ const latestSubscription = userSubscriptions
55
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0];
56
+
57
+ if (latestSubscription) {
58
+ const now = new Date();
59
+ const isExpired = new Date(latestSubscription.currentPeriodEnd) < now;
60
+ const isCanceled = latestSubscription.status === "canceled";
61
+
62
+ return {
63
+ hasSubscription: true,
64
+ subscription: {
65
+ id: latestSubscription.id,
66
+ productId: latestSubscription.productId,
67
+ status: latestSubscription.status,
68
+ amount: latestSubscription.amount,
69
+ currency: latestSubscription.currency,
70
+ recurringInterval: latestSubscription.recurringInterval,
71
+ currentPeriodStart: latestSubscription.currentPeriodStart,
72
+ currentPeriodEnd: latestSubscription.currentPeriodEnd,
73
+ cancelAtPeriodEnd: latestSubscription.cancelAtPeriodEnd,
74
+ canceledAt: latestSubscription.canceledAt,
75
+ organizationId: null,
76
+ },
77
+ error: isCanceled ? "Subscription has been canceled" : isExpired ? "Subscription has expired" : "Subscription is not active",
78
+ errorType: isCanceled ? "CANCELED" : isExpired ? "EXPIRED" : "GENERAL",
79
+ };
80
+ }
81
+
82
+ return { hasSubscription: false };
83
+ }
84
+
85
+ return {
86
+ hasSubscription: true,
87
+ subscription: {
88
+ id: activeSubscription.id,
89
+ productId: activeSubscription.productId,
90
+ status: activeSubscription.status,
91
+ amount: activeSubscription.amount,
92
+ currency: activeSubscription.currency,
93
+ recurringInterval: activeSubscription.recurringInterval,
94
+ currentPeriodStart: activeSubscription.currentPeriodStart,
95
+ currentPeriodEnd: activeSubscription.currentPeriodEnd,
96
+ cancelAtPeriodEnd: activeSubscription.cancelAtPeriodEnd,
97
+ canceledAt: activeSubscription.canceledAt,
98
+ organizationId: null,
99
+ },
100
+ };
101
+ } catch (error) {
102
+ console.error("Error fetching subscription details:", error);
103
+ return {
104
+ hasSubscription: false,
105
+ error: "Failed to load subscription details",
106
+ errorType: "GENERAL",
107
+ };
108
+ }
109
+ }
110
+
111
+ // Simple helper to check if user has an active subscription
112
+ export async function isUserSubscribed(): Promise<boolean> {
113
+ const result = await getSubscriptionDetails();
114
+ return result.hasSubscription && result.subscription?.status === "active";
115
+ }
116
+
117
+ // Helper to check if user has access to a specific product/tier
118
+ export async function hasAccessToProduct(productId: string): Promise<boolean> {
119
+ const result = await getSubscriptionDetails();
120
+ return (
121
+ result.hasSubscription &&
122
+ result.subscription?.status === "active" &&
123
+ result.subscription?.productId === productId
124
+ );
125
+ }
126
+
127
+ // Helper to get user's current subscription status
128
+ export async function getUserSubscriptionStatus(): Promise<"active" | "canceled" | "expired" | "none"> {
129
+ const result = await getSubscriptionDetails();
130
+
131
+ if (!result.hasSubscription) {
132
+ return "none";
133
+ }
134
+
135
+ if (result.subscription?.status === "active") {
136
+ return "active";
137
+ }
138
+
139
+ if (result.errorType === "CANCELED") {
140
+ return "canceled";
141
+ }
142
+
143
+ if (result.errorType === "EXPIRED") {
144
+ return "expired";
145
+ }
146
+
147
+ return "none";
148
+ }
@@ -0,0 +1,30 @@
1
+ import { pgTable, text, timestamp, boolean, integer } from "drizzle-orm/pg-core";
2
+
3
+ // Subscription table for Polar webhook data
4
+ // Note: userId references user.id from db/schema.ts (user table is added by auth module)
5
+ export const subscription = pgTable("subscription", {
6
+ id: text("id").primaryKey(),
7
+ createdAt: timestamp("createdAt").notNull(),
8
+ modifiedAt: timestamp("modifiedAt"),
9
+ amount: integer("amount").notNull(),
10
+ currency: text("currency").notNull(),
11
+ recurringInterval: text("recurringInterval").notNull(),
12
+ status: text("status").notNull(),
13
+ currentPeriodStart: timestamp("currentPeriodStart").notNull(),
14
+ currentPeriodEnd: timestamp("currentPeriodEnd").notNull(),
15
+ cancelAtPeriodEnd: boolean("cancelAtPeriodEnd").notNull().default(false),
16
+ canceledAt: timestamp("canceledAt"),
17
+ startedAt: timestamp("startedAt").notNull(),
18
+ endsAt: timestamp("endsAt"),
19
+ endedAt: timestamp("endedAt"),
20
+ customerId: text("customerId").notNull(),
21
+ productId: text("productId").notNull(),
22
+ discountId: text("discountId"),
23
+ checkoutId: text("checkoutId").notNull(),
24
+ customerCancellationReason: text("customerCancellationReason"),
25
+ customerCancellationComment: text("customerCancellationComment"),
26
+ metadata: text("metadata"), // JSON string
27
+ customFieldData: text("customFieldData"), // JSON string
28
+ userId: text("userId"), // References user.id (user table from auth module)
29
+ });
30
+
@@ -0,0 +1,302 @@
1
+ # SEO Module - Integration Guide
2
+
3
+ ## Overview
4
+
5
+ The SEO module provides a complete SEO foundation for your Next.js application, including automated sitemap generation, robots.txt, blog structure, and structured data utilities.
6
+
7
+ ## What's Included
8
+
9
+ ### Files Added
10
+
11
+ - `app/sitemap.ts` - Automated sitemap generation
12
+ - `app/robots.txt` - Search engine crawler instructions
13
+ - `app/blog/` - Complete blog structure with example posts
14
+ - `lib/seo-utils.ts` - Helper functions for structured data and metadata
15
+
16
+ ### Features
17
+
18
+ ✅ **Automated Sitemap Generation**
19
+ - Dynamically generates sitemap.xml
20
+ - Includes all major routes (home, blog, pricing, docs, etc.)
21
+ - Supports blog posts with proper priorities and change frequencies
22
+
23
+ ✅ **Robots.txt**
24
+ - Allows all crawlers by default
25
+ - Blocks sensitive routes (API, dashboard, auth pages)
26
+ - Points to sitemap location
27
+
28
+ ✅ **Blog Structure**
29
+ - Blog listing page (`/blog`)
30
+ - Individual blog post pages (`/blog/[slug]`)
31
+ - Markdown content support via react-markdown
32
+ - SEO-optimized metadata for each post
33
+
34
+ ✅ **Structured Data Utilities**
35
+ - JSON-LD generation for WebApplication
36
+ - BlogPosting structured data
37
+ - Organization schema
38
+ - OpenGraph and Twitter Card helpers
39
+
40
+ ## Dependencies
41
+
42
+ This module requires:
43
+ - `react-markdown` (for blog content)
44
+ - `remark-gfm` (for GitHub Flavored Markdown)
45
+
46
+ These are automatically added to your `package.json` when you append this module.
47
+
48
+ ## Environment Variables
49
+
50
+ Add to your `.env.local`:
51
+
52
+ ```env
53
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
54
+ ```
55
+
56
+ For production, set this to your actual domain:
57
+ ```env
58
+ NEXT_PUBLIC_APP_URL=https://yourdomain.com
59
+ ```
60
+
61
+ ## Manual Integration Steps
62
+
63
+ ### 1. Verify Files Were Copied
64
+
65
+ After appending, check that these files exist:
66
+ - `app/sitemap.ts`
67
+ - `app/robots.txt`
68
+ - `app/blog/page.tsx`
69
+ - `app/blog/[slug]/page.tsx`
70
+ - `lib/seo-utils.ts`
71
+
72
+ ### 2. Update Sitemap with Your Routes
73
+
74
+ Edit `app/sitemap.ts` to include all your application routes:
75
+
76
+ ```typescript
77
+ // Add your custom routes
78
+ {
79
+ url: `${baseUrl}/your-custom-page`,
80
+ lastModified: new Date(),
81
+ changeFrequency: 'monthly',
82
+ priority: 0.8,
83
+ }
84
+ ```
85
+
86
+ ### 3. Update Blog Posts
87
+
88
+ Edit `app/blog/page.tsx` and `app/blog/[slug]/page.tsx` to use your actual blog content. In production, you might:
89
+ - Fetch from a CMS (Contentful, Sanity, etc.)
90
+ - Read from markdown files
91
+ - Query from a database
92
+
93
+ ### 4. Add Structured Data to Layout
94
+
95
+ In your `app/layout.tsx`, add structured data:
96
+
97
+ ```typescript
98
+ import { generateWebAppStructuredData } from '@/lib/seo-utils';
99
+
100
+ export default function RootLayout({ children }) {
101
+ const jsonLd = generateWebAppStructuredData(
102
+ 'Your App Name',
103
+ 'Your app description',
104
+ 'https://yourdomain.com'
105
+ );
106
+
107
+ return (
108
+ <html>
109
+ <head>
110
+ <script
111
+ type="application/ld+json"
112
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
113
+ />
114
+ </head>
115
+ <body>{children}</body>
116
+ </html>
117
+ );
118
+ }
119
+ ```
120
+
121
+ ### 5. Add Structured Data to Blog Posts
122
+
123
+ In `app/blog/[slug]/page.tsx`, add blog post structured data:
124
+
125
+ ```typescript
126
+ import { generateBlogPostStructuredData } from '@/lib/seo-utils';
127
+
128
+ export default function BlogPost({ params }) {
129
+ const post = getPost(params.slug);
130
+
131
+ const jsonLd = generateBlogPostStructuredData(
132
+ post.title,
133
+ post.description,
134
+ `https://yourdomain.com/blog/${post.slug}`,
135
+ post.date,
136
+ post.dateModified,
137
+ post.author,
138
+ post.image
139
+ );
140
+
141
+ return (
142
+ <>
143
+ <script
144
+ type="application/ld+json"
145
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
146
+ />
147
+ {/* Blog post content */}
148
+ </>
149
+ );
150
+ }
151
+ ```
152
+
153
+ ## Usage Examples
154
+
155
+ ### Generate OpenGraph Metadata
156
+
157
+ ```typescript
158
+ import { generateOpenGraphMetadata } from '@/lib/seo-utils';
159
+
160
+ export const metadata = {
161
+ ...generateOpenGraphMetadata(
162
+ 'Page Title',
163
+ 'Page description',
164
+ 'https://yourdomain.com/page',
165
+ 'https://yourdomain.com/og-image.png'
166
+ ),
167
+ };
168
+ ```
169
+
170
+ ### Generate Twitter Card Metadata
171
+
172
+ ```typescript
173
+ import { generateTwitterCardMetadata } from '@/lib/seo-utils';
174
+
175
+ export const metadata = {
176
+ twitter: generateTwitterCardMetadata(
177
+ 'Page Title',
178
+ 'Page description',
179
+ 'https://yourdomain.com/twitter-image.png',
180
+ 'yourhandle'
181
+ ),
182
+ };
183
+ ```
184
+
185
+ ## Testing
186
+
187
+ 1. **Test Sitemap**: Visit `http://localhost:3000/sitemap.xml`
188
+ 2. **Test Robots.txt**: Visit `http://localhost:3000/robots.txt`
189
+ 3. **Test Blog**: Visit `http://localhost:3000/blog`
190
+ 4. **Test Blog Post**: Visit `http://localhost:3000/blog/getting-started-with-saas`
191
+
192
+ ## Customization
193
+
194
+ ### Update Robots.txt Rules
195
+
196
+ Edit `app/robots.txt` to customize crawler access:
197
+
198
+ ```
199
+ # Disallow specific paths
200
+ Disallow: /admin/
201
+ Disallow: /private/
202
+ ```
203
+
204
+ ### Add More Routes to Sitemap
205
+
206
+ Edit `app/sitemap.ts` and add your routes to the return array.
207
+
208
+ ### Customize Blog Structure
209
+
210
+ The blog pages are fully customizable. Update:
211
+ - `app/blog/page.tsx` - Blog listing page
212
+ - `app/blog/[slug]/page.tsx` - Individual blog post page
213
+
214
+ ## Related Modules
215
+
216
+ - **Marketing Landing Page** - Works great with SEO for discoverability
217
+ - **Documentation Pages** - Can be included in sitemap
218
+
219
+ ## Module Status
220
+
221
+ ✅ **Ready to Use** - Works immediately after append
222
+ ✅ **Template Variables** - Uses {{APP_URL}} in robots.txt
223
+ ✅ **No Conflicts** - Safe to append to any project
224
+
225
+ ## Troubleshooting
226
+
227
+ ### Sitemap Not Generating
228
+
229
+ - Ensure `NEXT_PUBLIC_APP_URL` is set in `.env.local`
230
+ - Check that `app/sitemap.ts` exists
231
+ - Verify Next.js version supports `MetadataRoute.Sitemap`
232
+
233
+ ### Blog Posts Not Showing
234
+
235
+ - Check that blog posts array is populated in `app/blog/page.tsx`
236
+ - Verify markdown content exists in `app/blog/[slug]/page.tsx`
237
+ - Ensure `react-markdown` and `remark-gfm` are installed
238
+
239
+ ### Structured Data Not Validating
240
+
241
+ - Use [Google's Rich Results Test](https://search.google.com/test/rich-results)
242
+ - Verify JSON-LD syntax is correct
243
+ - Check that required fields are present
244
+
245
+ ---
246
+
247
+ ## AI Integration Prompt
248
+
249
+ **For AI Assistants:** Use this prompt to understand and integrate the SEO module into an existing Next.js application.
250
+
251
+ ```
252
+ The SEO module has been appended to this Next.js project. Your task is to ensure it's properly integrated and functional.
253
+
254
+ **Installed Packages:**
255
+ - react-markdown@^10.1.0 - Markdown rendering for blog posts
256
+ - remark-gfm@^4.0.1 - GitHub Flavored Markdown support
257
+
258
+ **Files Added:**
259
+ - app/sitemap.ts - Automated sitemap generation (Next.js MetadataRoute)
260
+ - app/robots.txt - Search engine crawler instructions
261
+ - app/blog/page.tsx - Blog listing page with example posts
262
+ - app/blog/[slug]/page.tsx - Individual blog post page with markdown support
263
+ - lib/seo-utils.ts - Structured data helpers (JSON-LD, OpenGraph, Twitter Cards)
264
+
265
+ **Environment Variables Required:**
266
+ - NEXT_PUBLIC_APP_URL - Application URL (e.g., http://localhost:3000 or https://yourdomain.com)
267
+
268
+ **Integration Steps:**
269
+ 1. Verify app/sitemap.ts exists and exports default sitemap() function
270
+ 2. Check that app/robots.txt exists (Next.js serves this automatically)
271
+ 3. Verify blog structure exists: app/blog/page.tsx and app/blog/[slug]/page.tsx
272
+ 4. Check that lib/seo-utils.ts exists with structured data helper functions
273
+ 5. Ensure NEXT_PUBLIC_APP_URL is set in .env.local
274
+ 6. Test sitemap: Visit /sitemap.xml (should return XML)
275
+ 7. Test robots.txt: Visit /robots.txt (should return text)
276
+ 8. Test blog: Visit /blog (should show blog listing)
277
+
278
+ **Key Integration Points:**
279
+ - Sitemap generation: app/sitemap.ts uses Next.js MetadataRoute.Sitemap API
280
+ - Robots.txt: Next.js automatically serves app/robots.txt at /robots.txt
281
+ - Blog structure: Example blog posts are hardcoded; replace with CMS/database in production
282
+ - Structured data: Use lib/seo-utils.ts functions to generate JSON-LD for pages
283
+ - Template variables: robots.txt uses {{APP_URL}} which gets replaced during init
284
+
285
+ **Dependencies:**
286
+ - No dependencies on other Shipd modules
287
+ - Can be installed standalone
288
+
289
+ **Common Issues to Check:**
290
+ - If sitemap not generating: Check NEXT_PUBLIC_APP_URL is set, verify app/sitemap.ts exists
291
+ - If blog posts not showing: Check blog posts array in app/blog/page.tsx is populated
292
+ - If markdown not rendering: Ensure react-markdown and remark-gfm are installed
293
+ - If structured data invalid: Use Google Rich Results Test to validate JSON-LD
294
+
295
+ **Next Steps After Integration:**
296
+ - Update sitemap.ts with all your application routes
297
+ - Customize blog posts in app/blog/page.tsx and app/blog/[slug]/page.tsx
298
+ - Add structured data to app/layout.tsx using generateWebAppStructuredData()
299
+ - Add structured data to blog posts using generateBlogPostStructuredData()
300
+ - Update robots.txt rules if needed (currently allows all, blocks /api/, /dashboard/)
301
+ - Replace hardcoded blog posts with CMS/database queries in production
302
+ ```