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.
- package/base-package/app/globals.css +126 -0
- package/base-package/app/layout.tsx +53 -0
- package/base-package/app/page.tsx +15 -0
- package/base-package/base.config.json +57 -0
- package/base-package/components/ui/avatar.tsx +53 -0
- package/base-package/components/ui/badge.tsx +46 -0
- package/base-package/components/ui/button.tsx +59 -0
- package/base-package/components/ui/card.tsx +92 -0
- package/base-package/components/ui/chart.tsx +353 -0
- package/base-package/components/ui/checkbox.tsx +32 -0
- package/base-package/components/ui/dialog.tsx +135 -0
- package/base-package/components/ui/dropdown-menu.tsx +257 -0
- package/base-package/components/ui/form.tsx +167 -0
- package/base-package/components/ui/input.tsx +21 -0
- package/base-package/components/ui/label.tsx +24 -0
- package/base-package/components/ui/progress.tsx +31 -0
- package/base-package/components/ui/resizable.tsx +56 -0
- package/base-package/components/ui/select.tsx +185 -0
- package/base-package/components/ui/separator.tsx +28 -0
- package/base-package/components/ui/sheet.tsx +139 -0
- package/base-package/components/ui/skeleton.tsx +13 -0
- package/base-package/components/ui/sonner.tsx +25 -0
- package/base-package/components/ui/switch.tsx +31 -0
- package/base-package/components/ui/tabs.tsx +66 -0
- package/base-package/components/ui/textarea.tsx +18 -0
- package/base-package/components/ui/toggle-group.tsx +73 -0
- package/base-package/components/ui/toggle.tsx +47 -0
- package/base-package/components/ui/tooltip.tsx +61 -0
- package/base-package/components.json +21 -0
- package/base-package/eslint.config.mjs +16 -0
- package/base-package/lib/utils.ts +6 -0
- package/base-package/middleware.ts +12 -0
- package/base-package/next.config.ts +27 -0
- package/base-package/package.json +49 -0
- package/base-package/postcss.config.mjs +5 -0
- package/base-package/public/favicon.svg +4 -0
- package/base-package/tailwind.config.ts +89 -0
- package/base-package/tsconfig.json +27 -0
- package/dist/index.js +1858 -956
- package/docs-template/README.md +74 -0
- package/features/ai-chat/README.md +316 -0
- package/features/ai-chat/app/api/chat/route.ts +16 -0
- package/features/ai-chat/app/dashboard/_components/chatbot.tsx +39 -0
- package/features/ai-chat/app/dashboard/chat/page.tsx +73 -0
- package/features/ai-chat/feature.config.json +22 -0
- package/features/analytics/README.md +364 -0
- package/features/analytics/feature.config.json +20 -0
- package/features/analytics/lib/posthog.ts +36 -0
- package/features/auth/README.md +409 -0
- package/features/auth/app/api/auth/[...all]/route.ts +4 -0
- package/features/auth/app/dashboard/layout.tsx +15 -0
- package/features/auth/app/dashboard/page.tsx +140 -0
- package/features/auth/app/sign-in/page.tsx +228 -0
- package/features/auth/app/sign-up/page.tsx +243 -0
- package/features/auth/auth-schema.ts +47 -0
- package/features/auth/components/auth/setup-instructions.tsx +123 -0
- package/features/auth/feature.config.json +33 -0
- package/features/auth/lib/auth-client.ts +8 -0
- package/features/auth/lib/auth.ts +295 -0
- package/features/auth/lib/email-stub.ts +55 -0
- package/features/auth/lib/email.ts +47 -0
- package/features/auth/middleware.patch.ts +43 -0
- package/features/database/README.md +312 -0
- package/features/database/db/drizzle.ts +48 -0
- package/features/database/db/schema.ts +21 -0
- package/features/database/drizzle.config.ts +13 -0
- package/features/database/feature.config.json +30 -0
- package/features/email/README.md +341 -0
- package/features/email/emails/components/layout.tsx +181 -0
- package/features/email/emails/password-reset.tsx +67 -0
- package/features/email/emails/payment-failed.tsx +167 -0
- package/features/email/emails/subscription-confirmation.tsx +129 -0
- package/features/email/emails/welcome.tsx +100 -0
- package/features/email/feature.config.json +22 -0
- package/features/email/lib/email.ts +118 -0
- package/features/file-upload/README.md +329 -0
- package/features/file-upload/app/api/upload-image/route.ts +64 -0
- package/features/file-upload/app/dashboard/upload/page.tsx +324 -0
- package/features/file-upload/feature.config.json +23 -0
- package/features/file-upload/lib/upload-image.ts +28 -0
- package/features/marketing-landing/README.md +333 -0
- package/features/marketing-landing/app/page.tsx +25 -0
- package/features/marketing-landing/components/homepage/cli-workflow-section.tsx +231 -0
- package/features/marketing-landing/components/homepage/features-section.tsx +152 -0
- package/features/marketing-landing/components/homepage/footer.tsx +53 -0
- package/features/marketing-landing/components/homepage/hero-section.tsx +112 -0
- package/features/marketing-landing/components/homepage/integrations.tsx +124 -0
- package/features/marketing-landing/components/homepage/navigation.tsx +116 -0
- package/features/marketing-landing/components/homepage/news-section.tsx +82 -0
- package/features/marketing-landing/components/homepage/pricing-section.tsx +98 -0
- package/features/marketing-landing/components/homepage/testimonials-section.tsx +34 -0
- package/features/marketing-landing/components/logos/BetterAuth.tsx +21 -0
- package/features/marketing-landing/components/logos/NeonPostgres.tsx +41 -0
- package/features/marketing-landing/components/logos/Nextjs.tsx +72 -0
- package/features/marketing-landing/components/logos/Polar.tsx +7 -0
- package/features/marketing-landing/components/logos/TailwindCSS.tsx +27 -0
- package/features/marketing-landing/components/logos/index.ts +6 -0
- package/features/marketing-landing/components/logos/shadcnui.tsx +8 -0
- package/features/marketing-landing/feature.config.json +23 -0
- package/features/payments/README.md +375 -0
- package/features/payments/app/api/subscription/route.ts +25 -0
- package/features/payments/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
- package/features/payments/app/dashboard/payment/page.tsx +126 -0
- package/features/payments/app/success/page.tsx +123 -0
- package/features/payments/feature.config.json +31 -0
- package/features/payments/lib/polar-products.ts +49 -0
- package/features/payments/lib/subscription.ts +148 -0
- package/features/payments/payments-schema.ts +30 -0
- package/features/seo/README.md +302 -0
- package/features/seo/app/blog/[slug]/page.tsx +314 -0
- package/features/seo/app/blog/page.tsx +107 -0
- package/features/seo/app/robots.txt +13 -0
- package/features/seo/app/sitemap.ts +70 -0
- package/features/seo/feature.config.json +19 -0
- package/features/seo/lib/seo-utils.ts +163 -0
- package/package.json +3 -1
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { notFound } from "next/navigation";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { ArrowLeft, Calendar, Clock } from "lucide-react";
|
|
4
|
+
import ReactMarkdown from "react-markdown";
|
|
5
|
+
import remarkGfm from "remark-gfm";
|
|
6
|
+
|
|
7
|
+
// Blog posts content
|
|
8
|
+
// In production, this would come from a CMS, database, or markdown files
|
|
9
|
+
const blogPosts: Record<string, {
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
date: string;
|
|
13
|
+
author: string;
|
|
14
|
+
readTime: string;
|
|
15
|
+
category: string;
|
|
16
|
+
content: string;
|
|
17
|
+
}> = {
|
|
18
|
+
"getting-started-with-saas": {
|
|
19
|
+
title: "Getting Started with Your SaaS Journey",
|
|
20
|
+
description: "Learn how to validate your SaaS idea and build your first MVP in record time.",
|
|
21
|
+
date: "2024-12-15",
|
|
22
|
+
author: "{{PROJECT_NAME}} Team",
|
|
23
|
+
readTime: "5 min read",
|
|
24
|
+
category: "Guides",
|
|
25
|
+
content: `
|
|
26
|
+
# Getting Started with Your SaaS Journey
|
|
27
|
+
|
|
28
|
+
Building a successful SaaS product starts with validation. Here's how to get started quickly and efficiently.
|
|
29
|
+
|
|
30
|
+
## 1. Validate Your Idea
|
|
31
|
+
|
|
32
|
+
Before writing a single line of code, talk to potential customers. Understanding their pain points is crucial.
|
|
33
|
+
|
|
34
|
+
### Key Questions to Ask:
|
|
35
|
+
- What problem are they currently facing?
|
|
36
|
+
- How are they solving it today?
|
|
37
|
+
- Would they pay for a better solution?
|
|
38
|
+
|
|
39
|
+
## 2. Build Your MVP
|
|
40
|
+
|
|
41
|
+
Focus on the core features that solve the main problem. Don't get caught up in building everything at once.
|
|
42
|
+
|
|
43
|
+
**Essential MVP features:**
|
|
44
|
+
- User authentication
|
|
45
|
+
- Core functionality
|
|
46
|
+
- Basic payment integration
|
|
47
|
+
- Simple dashboard
|
|
48
|
+
|
|
49
|
+
## 3. Get Your First Customers
|
|
50
|
+
|
|
51
|
+
Launch early and iterate based on feedback. Your first 10 customers will teach you more than any market research.
|
|
52
|
+
|
|
53
|
+
> "The best way to predict the future is to create it." - Peter Drucker
|
|
54
|
+
|
|
55
|
+
## Next Steps
|
|
56
|
+
|
|
57
|
+
Ready to build? Check out our comprehensive starter template that includes everything you need to launch your SaaS in days, not months.
|
|
58
|
+
`,
|
|
59
|
+
},
|
|
60
|
+
"optimizing-conversion-rates": {
|
|
61
|
+
title: "10 Proven Ways to Optimize Your SaaS Conversion Rates",
|
|
62
|
+
description: "Discover actionable strategies to convert more visitors into paying customers.",
|
|
63
|
+
date: "2024-12-10",
|
|
64
|
+
author: "{{PROJECT_NAME}} Team",
|
|
65
|
+
readTime: "8 min read",
|
|
66
|
+
category: "Marketing",
|
|
67
|
+
content: `
|
|
68
|
+
# 10 Proven Ways to Optimize Your SaaS Conversion Rates
|
|
69
|
+
|
|
70
|
+
Increasing your conversion rate can dramatically impact your revenue. Here are 10 proven strategies.
|
|
71
|
+
|
|
72
|
+
## 1. Simplify Your Signup Flow
|
|
73
|
+
|
|
74
|
+
Remove unnecessary fields. Every additional field reduces conversions by an average of 11%.
|
|
75
|
+
|
|
76
|
+
## 2. Add Social Proof
|
|
77
|
+
|
|
78
|
+
Display customer testimonials, logos, and case studies prominently on your landing page.
|
|
79
|
+
|
|
80
|
+
## 3. Offer a Free Trial
|
|
81
|
+
|
|
82
|
+
Let users experience your product before asking for payment. Free trials can increase conversions by up to 300%.
|
|
83
|
+
|
|
84
|
+
## 4. Optimize Your Pricing Page
|
|
85
|
+
|
|
86
|
+
Make your pricing transparent and easy to understand. Highlight the most popular plan.
|
|
87
|
+
|
|
88
|
+
## 5. Use Clear CTAs
|
|
89
|
+
|
|
90
|
+
Your call-to-action buttons should be specific. Instead of "Submit," use "Start Your Free Trial."
|
|
91
|
+
|
|
92
|
+
## 6. Implement Exit-Intent Popups
|
|
93
|
+
|
|
94
|
+
Catch users before they leave with a special offer or helpful resource.
|
|
95
|
+
|
|
96
|
+
## 7. A/B Test Everything
|
|
97
|
+
|
|
98
|
+
Test your headlines, CTAs, colors, and copy. Small changes can lead to big improvements.
|
|
99
|
+
|
|
100
|
+
## 8. Reduce Page Load Time
|
|
101
|
+
|
|
102
|
+
Every second of delay can cost you 7% in conversions. Optimize for speed.
|
|
103
|
+
|
|
104
|
+
## 9. Mobile Optimization
|
|
105
|
+
|
|
106
|
+
Over 50% of traffic is mobile. Ensure your site works perfectly on all devices.
|
|
107
|
+
|
|
108
|
+
## 10. Follow Up Quickly
|
|
109
|
+
|
|
110
|
+
Respond to inquiries within 5 minutes. The odds of qualifying a lead decrease by 400% after that.
|
|
111
|
+
|
|
112
|
+
## Conclusion
|
|
113
|
+
|
|
114
|
+
Implementing these strategies systematically can double or triple your conversion rates. Start with the quickest wins and iterate from there.
|
|
115
|
+
`,
|
|
116
|
+
},
|
|
117
|
+
"scaling-your-saas": {
|
|
118
|
+
title: "Scaling Your SaaS: Infrastructure Best Practices",
|
|
119
|
+
description: "Technical insights on scaling your application to handle thousands of users.",
|
|
120
|
+
date: "2024-12-05",
|
|
121
|
+
author: "{{PROJECT_NAME}} Team",
|
|
122
|
+
readTime: "10 min read",
|
|
123
|
+
category: "Engineering",
|
|
124
|
+
content: `
|
|
125
|
+
# Scaling Your SaaS: Infrastructure Best Practices
|
|
126
|
+
|
|
127
|
+
As your SaaS grows, you'll need to scale your infrastructure. Here's how to do it right.
|
|
128
|
+
|
|
129
|
+
## Database Optimization
|
|
130
|
+
|
|
131
|
+
### Connection Pooling
|
|
132
|
+
Use connection pooling to manage database connections efficiently. Tools like PgBouncer can help.
|
|
133
|
+
|
|
134
|
+
\`\`\`typescript
|
|
135
|
+
// Example: Using connection pooling with Drizzle
|
|
136
|
+
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
137
|
+
import postgres from 'postgres';
|
|
138
|
+
|
|
139
|
+
const client = postgres(process.env.DATABASE_URL!, { max: 10 });
|
|
140
|
+
const db = drizzle(client);
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
## Caching Strategy
|
|
144
|
+
|
|
145
|
+
Implement multi-layer caching:
|
|
146
|
+
1. **Browser caching** for static assets
|
|
147
|
+
2. **CDN caching** for global distribution
|
|
148
|
+
3. **Redis caching** for frequently accessed data
|
|
149
|
+
|
|
150
|
+
## Load Balancing
|
|
151
|
+
|
|
152
|
+
Distribute traffic across multiple servers to handle increased load and provide redundancy.
|
|
153
|
+
|
|
154
|
+
## Monitoring & Observability
|
|
155
|
+
|
|
156
|
+
Track these key metrics:
|
|
157
|
+
- Response times
|
|
158
|
+
- Error rates
|
|
159
|
+
- Database query performance
|
|
160
|
+
- Memory usage
|
|
161
|
+
- CPU utilization
|
|
162
|
+
|
|
163
|
+
## Horizontal Scaling
|
|
164
|
+
|
|
165
|
+
Design your application to scale horizontally by adding more servers rather than upgrading existing ones.
|
|
166
|
+
|
|
167
|
+
### Stateless Design
|
|
168
|
+
Ensure your application doesn't store session state on individual servers. Use Redis or a database for session management.
|
|
169
|
+
|
|
170
|
+
## Conclusion
|
|
171
|
+
|
|
172
|
+
Scaling is an ongoing process. Start with good fundamentals and iterate as you grow. Monitor your metrics and optimize bottlenecks as they appear.
|
|
173
|
+
`,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export async function generateStaticParams() {
|
|
178
|
+
return Object.keys(blogPosts).map((slug) => ({
|
|
179
|
+
slug,
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function generateMetadata({ params }: { params: { slug: string } }) {
|
|
184
|
+
const post = blogPosts[params.slug];
|
|
185
|
+
|
|
186
|
+
if (!post) {
|
|
187
|
+
return {
|
|
188
|
+
title: "Post Not Found",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
title: post.title,
|
|
194
|
+
description: post.description,
|
|
195
|
+
openGraph: {
|
|
196
|
+
title: post.title,
|
|
197
|
+
description: post.description,
|
|
198
|
+
type: "article",
|
|
199
|
+
publishedTime: post.date,
|
|
200
|
+
authors: [post.author],
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default function BlogPost({ params }: { params: { slug: string } }) {
|
|
206
|
+
const post = blogPosts[params.slug];
|
|
207
|
+
|
|
208
|
+
if (!post) {
|
|
209
|
+
notFound();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className="min-h-screen bg-black">
|
|
214
|
+
{/* Back Link */}
|
|
215
|
+
<div className="border-b border-[#2a2a2a]">
|
|
216
|
+
<div className="container mx-auto px-4 py-6 sm:px-6 lg:px-8">
|
|
217
|
+
<Link
|
|
218
|
+
href="/blog"
|
|
219
|
+
className="inline-flex items-center text-sm text-gray-400 hover:text-[#ff5722] transition-colors"
|
|
220
|
+
>
|
|
221
|
+
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
222
|
+
Back to Blog
|
|
223
|
+
</Link>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
{/* Article Header */}
|
|
228
|
+
<article className="container mx-auto px-4 py-12 sm:px-6 lg:px-8">
|
|
229
|
+
<div className="mx-auto max-w-3xl">
|
|
230
|
+
<header className="mb-12">
|
|
231
|
+
<div className="mb-4">
|
|
232
|
+
<span className="inline-flex items-center rounded-full bg-[#ff5722]/10 px-3 py-1 text-sm font-medium text-[#ff5722] border border-[#ff5722]/20">
|
|
233
|
+
{post.category}
|
|
234
|
+
</span>
|
|
235
|
+
</div>
|
|
236
|
+
<h1 className="mb-4 text-4xl font-bold tracking-tight text-white sm:text-5xl">
|
|
237
|
+
{post.title}
|
|
238
|
+
</h1>
|
|
239
|
+
<p className="text-xl text-gray-400">{post.description}</p>
|
|
240
|
+
<div className="mt-6 flex items-center gap-4 text-sm text-gray-500">
|
|
241
|
+
<div className="flex items-center gap-2">
|
|
242
|
+
<Calendar className="h-4 w-4" />
|
|
243
|
+
<time dateTime={post.date}>
|
|
244
|
+
{new Date(post.date).toLocaleDateString("en-US", {
|
|
245
|
+
year: "numeric",
|
|
246
|
+
month: "long",
|
|
247
|
+
day: "numeric",
|
|
248
|
+
})}
|
|
249
|
+
</time>
|
|
250
|
+
</div>
|
|
251
|
+
<span>•</span>
|
|
252
|
+
<div className="flex items-center gap-2">
|
|
253
|
+
<Clock className="h-4 w-4" />
|
|
254
|
+
<span>{post.readTime}</span>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</header>
|
|
258
|
+
|
|
259
|
+
{/* Article Content */}
|
|
260
|
+
<div className="prose prose-invert prose-lg max-w-none">
|
|
261
|
+
<ReactMarkdown
|
|
262
|
+
remarkPlugins={[remarkGfm]}
|
|
263
|
+
components={{
|
|
264
|
+
h1: ({ children }) => (
|
|
265
|
+
<h1 className="text-3xl font-bold text-white mt-8 mb-4">{children}</h1>
|
|
266
|
+
),
|
|
267
|
+
h2: ({ children }) => (
|
|
268
|
+
<h2 className="text-2xl font-bold text-white mt-8 mb-4">{children}</h2>
|
|
269
|
+
),
|
|
270
|
+
h3: ({ children }) => (
|
|
271
|
+
<h3 className="text-xl font-bold text-white mt-6 mb-3">{children}</h3>
|
|
272
|
+
),
|
|
273
|
+
p: ({ children }) => (
|
|
274
|
+
<p className="text-gray-300 mb-4 leading-relaxed">{children}</p>
|
|
275
|
+
),
|
|
276
|
+
ul: ({ children }) => (
|
|
277
|
+
<ul className="list-disc list-inside text-gray-300 mb-4 space-y-2">{children}</ul>
|
|
278
|
+
),
|
|
279
|
+
ol: ({ children }) => (
|
|
280
|
+
<ol className="list-decimal list-inside text-gray-300 mb-4 space-y-2">{children}</ol>
|
|
281
|
+
),
|
|
282
|
+
blockquote: ({ children }) => (
|
|
283
|
+
<blockquote className="border-l-4 border-[#ff5722] pl-4 italic text-gray-400 my-6">
|
|
284
|
+
{children}
|
|
285
|
+
</blockquote>
|
|
286
|
+
),
|
|
287
|
+
code: ({ children, className }) => {
|
|
288
|
+
const isBlock = className?.includes('language-');
|
|
289
|
+
if (isBlock) {
|
|
290
|
+
return (
|
|
291
|
+
<pre className="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-4 overflow-x-auto my-6">
|
|
292
|
+
<code className="text-sm text-gray-300">{children}</code>
|
|
293
|
+
</pre>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return (
|
|
297
|
+
<code className="bg-[#1a1a1a] text-[#ff5722] px-1.5 py-0.5 rounded text-sm">
|
|
298
|
+
{children}
|
|
299
|
+
</code>
|
|
300
|
+
);
|
|
301
|
+
},
|
|
302
|
+
strong: ({ children }) => (
|
|
303
|
+
<strong className="font-bold text-white">{children}</strong>
|
|
304
|
+
),
|
|
305
|
+
}}
|
|
306
|
+
>
|
|
307
|
+
{post.content}
|
|
308
|
+
</ReactMarkdown>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
</article>
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
3
|
+
import { ArrowRight, Calendar } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
// Blog posts metadata
|
|
6
|
+
// In a real app, this would come from a CMS or markdown files
|
|
7
|
+
const blogPosts = [
|
|
8
|
+
{
|
|
9
|
+
slug: "getting-started-with-saas",
|
|
10
|
+
title: "Getting Started with Your SaaS Journey",
|
|
11
|
+
description: "Learn how to validate your SaaS idea and build your first MVP in record time.",
|
|
12
|
+
date: "2024-12-15",
|
|
13
|
+
author: "{{PROJECT_NAME}} Team",
|
|
14
|
+
readTime: "5 min read",
|
|
15
|
+
category: "Guides",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
slug: "optimizing-conversion-rates",
|
|
19
|
+
title: "10 Proven Ways to Optimize Your SaaS Conversion Rates",
|
|
20
|
+
description: "Discover actionable strategies to convert more visitors into paying customers.",
|
|
21
|
+
date: "2024-12-10",
|
|
22
|
+
author: "{{PROJECT_NAME}} Team",
|
|
23
|
+
readTime: "8 min read",
|
|
24
|
+
category: "Marketing",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
slug: "scaling-your-saas",
|
|
28
|
+
title: "Scaling Your SaaS: Infrastructure Best Practices",
|
|
29
|
+
description: "Technical insights on scaling your application to handle thousands of users.",
|
|
30
|
+
date: "2024-12-05",
|
|
31
|
+
author: "{{PROJECT_NAME}} Team",
|
|
32
|
+
readTime: "10 min read",
|
|
33
|
+
category: "Engineering",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export const metadata = {
|
|
38
|
+
title: "Blog",
|
|
39
|
+
description: "Insights, guides, and best practices for building successful SaaS products.",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default function BlogPage() {
|
|
43
|
+
return (
|
|
44
|
+
<div className="min-h-screen bg-black">
|
|
45
|
+
{/* Header */}
|
|
46
|
+
<div className="border-b border-[#2a2a2a] bg-gradient-to-b from-[#0a0a0a] to-black">
|
|
47
|
+
<div className="container mx-auto px-4 py-16 sm:px-6 lg:px-8">
|
|
48
|
+
<div className="max-w-3xl">
|
|
49
|
+
<h1 className="text-4xl font-bold tracking-tight text-white sm:text-5xl md:text-6xl">
|
|
50
|
+
Blog
|
|
51
|
+
</h1>
|
|
52
|
+
<p className="mt-4 text-xl text-gray-400">
|
|
53
|
+
Insights, guides, and best practices for building successful SaaS products.
|
|
54
|
+
</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Blog Posts Grid */}
|
|
60
|
+
<div className="container mx-auto px-4 py-16 sm:px-6 lg:px-8">
|
|
61
|
+
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
|
62
|
+
{blogPosts.map((post) => (
|
|
63
|
+
<Link key={post.slug} href={`/blog/${post.slug}`}>
|
|
64
|
+
<Card className="h-full bg-[#0a0a0a] border-[#2a2a2a] hover:border-[#ff5722]/30 transition-all duration-300 group">
|
|
65
|
+
<CardHeader>
|
|
66
|
+
<div className="flex items-center gap-2 text-sm text-gray-500 mb-2">
|
|
67
|
+
<Calendar className="h-4 w-4" />
|
|
68
|
+
<time dateTime={post.date}>
|
|
69
|
+
{new Date(post.date).toLocaleDateString("en-US", {
|
|
70
|
+
year: "numeric",
|
|
71
|
+
month: "long",
|
|
72
|
+
day: "numeric",
|
|
73
|
+
})}
|
|
74
|
+
</time>
|
|
75
|
+
<span>•</span>
|
|
76
|
+
<span>{post.readTime}</span>
|
|
77
|
+
</div>
|
|
78
|
+
<CardTitle className="text-white group-hover:text-[#ff5722] transition-colors">
|
|
79
|
+
{post.title}
|
|
80
|
+
</CardTitle>
|
|
81
|
+
<CardDescription className="text-gray-400">
|
|
82
|
+
{post.description}
|
|
83
|
+
</CardDescription>
|
|
84
|
+
</CardHeader>
|
|
85
|
+
<CardContent>
|
|
86
|
+
<div className="flex items-center justify-between">
|
|
87
|
+
<span className="inline-flex items-center rounded-full bg-[#ff5722]/10 px-3 py-1 text-xs font-medium text-[#ff5722] border border-[#ff5722]/20">
|
|
88
|
+
{post.category}
|
|
89
|
+
</span>
|
|
90
|
+
<ArrowRight className="h-4 w-4 text-gray-500 group-hover:text-[#ff5722] group-hover:translate-x-1 transition-all" />
|
|
91
|
+
</div>
|
|
92
|
+
</CardContent>
|
|
93
|
+
</Card>
|
|
94
|
+
</Link>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Coming Soon Message */}
|
|
99
|
+
<div className="mt-12 text-center">
|
|
100
|
+
<p className="text-gray-500">
|
|
101
|
+
More articles coming soon. Subscribe to our newsletter to stay updated!
|
|
102
|
+
</p>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { MetadataRoute } from 'next'
|
|
2
|
+
|
|
3
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
4
|
+
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
|
5
|
+
|
|
6
|
+
// Blog posts - update this array with your actual blog posts
|
|
7
|
+
// In production, you might fetch this from a CMS or markdown files
|
|
8
|
+
const blogPosts = [
|
|
9
|
+
{
|
|
10
|
+
slug: 'getting-started-with-saas',
|
|
11
|
+
date: '2024-12-15',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
slug: 'optimizing-conversion-rates',
|
|
15
|
+
date: '2024-12-10',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
slug: 'scaling-your-saas',
|
|
19
|
+
date: '2024-12-05',
|
|
20
|
+
},
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const blogRoutes = blogPosts.map((post) => ({
|
|
24
|
+
url: `${baseUrl}/blog/${post.slug}`,
|
|
25
|
+
lastModified: new Date(post.date),
|
|
26
|
+
changeFrequency: 'monthly' as const,
|
|
27
|
+
priority: 0.7,
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
url: baseUrl,
|
|
33
|
+
lastModified: new Date(),
|
|
34
|
+
changeFrequency: 'weekly',
|
|
35
|
+
priority: 1,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
url: `${baseUrl}/blog`,
|
|
39
|
+
lastModified: new Date(),
|
|
40
|
+
changeFrequency: 'weekly',
|
|
41
|
+
priority: 0.9,
|
|
42
|
+
},
|
|
43
|
+
...blogRoutes,
|
|
44
|
+
{
|
|
45
|
+
url: `${baseUrl}/pricing`,
|
|
46
|
+
lastModified: new Date(),
|
|
47
|
+
changeFrequency: 'monthly',
|
|
48
|
+
priority: 0.8,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
url: `${baseUrl}/docs`,
|
|
52
|
+
lastModified: new Date(),
|
|
53
|
+
changeFrequency: 'weekly',
|
|
54
|
+
priority: 0.8,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
url: `${baseUrl}/terms-of-service`,
|
|
58
|
+
lastModified: new Date(),
|
|
59
|
+
changeFrequency: 'yearly',
|
|
60
|
+
priority: 0.3,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
url: `${baseUrl}/privacy-policy`,
|
|
64
|
+
lastModified: new Date(),
|
|
65
|
+
changeFrequency: 'yearly',
|
|
66
|
+
priority: 0.3,
|
|
67
|
+
},
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "seo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Complete SEO module with sitemap, robots.txt, blog structure, and structured data",
|
|
5
|
+
"dependencies": {},
|
|
6
|
+
"devDependencies": {},
|
|
7
|
+
"envVars": [
|
|
8
|
+
"NEXT_PUBLIC_APP_URL"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
"app/sitemap.ts",
|
|
12
|
+
"app/robots.txt",
|
|
13
|
+
"app/blog/**/*",
|
|
14
|
+
"lib/seo-utils.ts"
|
|
15
|
+
],
|
|
16
|
+
"requires": [],
|
|
17
|
+
"conflicts": []
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO Utilities
|
|
3
|
+
* Helper functions for structured data, meta tags, and SEO optimization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface StructuredData {
|
|
7
|
+
"@context": string;
|
|
8
|
+
"@type": string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate JSON-LD structured data for a WebApplication
|
|
14
|
+
*/
|
|
15
|
+
export function generateWebAppStructuredData(
|
|
16
|
+
name: string,
|
|
17
|
+
description: string,
|
|
18
|
+
url: string
|
|
19
|
+
): StructuredData {
|
|
20
|
+
return {
|
|
21
|
+
"@context": "https://schema.org",
|
|
22
|
+
"@type": "WebApplication",
|
|
23
|
+
name,
|
|
24
|
+
description,
|
|
25
|
+
url,
|
|
26
|
+
applicationCategory: "BusinessApplication",
|
|
27
|
+
offers: {
|
|
28
|
+
"@type": "Offer",
|
|
29
|
+
category: "SaaS",
|
|
30
|
+
},
|
|
31
|
+
creator: {
|
|
32
|
+
"@type": "Organization",
|
|
33
|
+
name,
|
|
34
|
+
url,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generate JSON-LD structured data for a BlogPosting
|
|
41
|
+
*/
|
|
42
|
+
export function generateBlogPostStructuredData(
|
|
43
|
+
title: string,
|
|
44
|
+
description: string,
|
|
45
|
+
url: string,
|
|
46
|
+
datePublished: string,
|
|
47
|
+
dateModified?: string,
|
|
48
|
+
author?: string,
|
|
49
|
+
image?: string
|
|
50
|
+
): StructuredData {
|
|
51
|
+
return {
|
|
52
|
+
"@context": "https://schema.org",
|
|
53
|
+
"@type": "BlogPosting",
|
|
54
|
+
headline: title,
|
|
55
|
+
description,
|
|
56
|
+
url,
|
|
57
|
+
datePublished,
|
|
58
|
+
dateModified: dateModified || datePublished,
|
|
59
|
+
author: author
|
|
60
|
+
? {
|
|
61
|
+
"@type": "Person",
|
|
62
|
+
name: author,
|
|
63
|
+
}
|
|
64
|
+
: undefined,
|
|
65
|
+
image: image
|
|
66
|
+
? {
|
|
67
|
+
"@type": "ImageObject",
|
|
68
|
+
url: image,
|
|
69
|
+
}
|
|
70
|
+
: undefined,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generate JSON-LD structured data for an Organization
|
|
76
|
+
*/
|
|
77
|
+
export function generateOrganizationStructuredData(
|
|
78
|
+
name: string,
|
|
79
|
+
url: string,
|
|
80
|
+
logo?: string,
|
|
81
|
+
socialProfiles?: {
|
|
82
|
+
twitter?: string;
|
|
83
|
+
github?: string;
|
|
84
|
+
linkedin?: string;
|
|
85
|
+
}
|
|
86
|
+
): StructuredData {
|
|
87
|
+
const data: StructuredData = {
|
|
88
|
+
"@context": "https://schema.org",
|
|
89
|
+
"@type": "Organization",
|
|
90
|
+
name,
|
|
91
|
+
url,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (logo) {
|
|
95
|
+
data.logo = {
|
|
96
|
+
"@type": "ImageObject",
|
|
97
|
+
url: logo,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (socialProfiles) {
|
|
102
|
+
data.sameAs = [];
|
|
103
|
+
if (socialProfiles.twitter) {
|
|
104
|
+
data.sameAs.push(socialProfiles.twitter);
|
|
105
|
+
}
|
|
106
|
+
if (socialProfiles.github) {
|
|
107
|
+
data.sameAs.push(socialProfiles.github);
|
|
108
|
+
}
|
|
109
|
+
if (socialProfiles.linkedin) {
|
|
110
|
+
data.sameAs.push(socialProfiles.linkedin);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate OpenGraph metadata object
|
|
119
|
+
*/
|
|
120
|
+
export function generateOpenGraphMetadata(
|
|
121
|
+
title: string,
|
|
122
|
+
description: string,
|
|
123
|
+
url: string,
|
|
124
|
+
image?: string,
|
|
125
|
+
type: "website" | "article" = "website"
|
|
126
|
+
) {
|
|
127
|
+
return {
|
|
128
|
+
title,
|
|
129
|
+
description,
|
|
130
|
+
url,
|
|
131
|
+
siteName: title,
|
|
132
|
+
images: image
|
|
133
|
+
? [
|
|
134
|
+
{
|
|
135
|
+
url: image,
|
|
136
|
+
width: 1200,
|
|
137
|
+
height: 630,
|
|
138
|
+
alt: title,
|
|
139
|
+
},
|
|
140
|
+
]
|
|
141
|
+
: [],
|
|
142
|
+
type,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generate Twitter Card metadata
|
|
148
|
+
*/
|
|
149
|
+
export function generateTwitterCardMetadata(
|
|
150
|
+
title: string,
|
|
151
|
+
description: string,
|
|
152
|
+
image?: string,
|
|
153
|
+
creator?: string
|
|
154
|
+
) {
|
|
155
|
+
return {
|
|
156
|
+
card: "summary_large_image" as const,
|
|
157
|
+
title,
|
|
158
|
+
description,
|
|
159
|
+
images: image ? [image] : [],
|
|
160
|
+
creator: creator ? `@${creator}` : undefined,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Generate production-ready SaaS applications with authentication, billing, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"dist",
|
|
12
12
|
"template",
|
|
13
13
|
"docs-template",
|
|
14
|
+
"features",
|
|
15
|
+
"base-package",
|
|
14
16
|
"README.md",
|
|
15
17
|
"LICENSE"
|
|
16
18
|
],
|