După ce ai implementat funcționalitățile de bază — articole, autentificare, afișare listă — e timpul să trecem la lucruri serioase: funcționalități avansate care îmbunătățesc experiența utilizatorului și profesionalizează aplicația.
În acest articol vom acoperi:
- 
Comentarii și relații între entități (Post ↔ Comments)
 - 
Paginare, căutare și filtrare articole
 - 
Upload imagine cover pentru articol (multipart/form-data)
 - 
SEO: SSR vs SPA – decizii pentru un blog modern
 
💬 1. Comentarii și relații între entități (Post ↔ Comments)
✅ Modelare în backend
 public class Post
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public ICollection<Comment> Comments { get; set; }
}
public class Comment
{
    public Guid Id { get; set; }
    public string AuthorName { get; set; }
    public string Text { get; set; }
    public DateTime CreatedAt { get; set; }
    public Guid PostId { get; set; }
    public Post Post { get; set; }
}
[HttpGet("{id}")]
public async Task<IActionResult> GetPostWithComments(Guid id)
{
    var post = await _context.Posts
        .Include(p => p.Comments)
        .FirstOrDefaultAsync(p => p.Id == id);
    return Ok(post);
}
[HttpPost("{postId}/comments")]
public async Task<IActionResult> AddComment(Guid postId, [FromBody] CommentDto dto)
{
    var comment = new Comment { Text = dto.Text, PostId = postId, CreatedAt = DateTime.UtcNow };
    _context.Comments.Add(comment);
    await _context.SaveChangesAsync();
    return Ok();
}
✅ React
const PostPage = () => {
  const [post, setPost] = useState<Post | null>(null);
  useEffect(() => {
    fetch(`/api/posts/${id}`).then(res => res.json()).then(setPost);
  }, []);
  return (
    <>
      <h1>{post?.title}</h1>
      <p>{post?.content}</p>
      <h3>Comentarii</h3>
      <ul>
        {post?.comments.map(c => (
          <li key={c.id}><strong>{c.authorName}</strong>: {c.text}</li>
        ))}
      </ul>
    </>
  );
};
🔍 2. Paginare, căutare și filtrare articole
✅ Backend
 [HttpGet]
public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] string? search = null)
{
    var query = _context.Posts.AsQueryable();
    if (!string.IsNullOrEmpty(search))
        query = query.Where(p => p.Title.Contains(search));
    var pageSize = 10;
    var items = await query
        .OrderByDescending(p => p.CreatedAt)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();
    return Ok(items);
}
const [search, setSearch] = useState('');
const [page, setPage] = useState(1);
useEffect(() => {
  fetch(`/api/posts?page=${page}&search=${search}`)
    .then(res => res.json())
    .then(setPosts);
}, [page, search]);
✅ Poți adăuga componente ca:
🖼️ 3. Upload imagine cover pentru articol
✅ Backend (multipart/form-data)
 [HttpPost]
public async Task<IActionResult> Upload([FromForm] CreatePostWithImageDto dto)
{
    var file = dto.Image;
    var fileName = Guid.NewGuid() + Path.GetExtension(file.FileName);
    var path = Path.Combine("uploads", fileName);
    using var stream = new FileStream(path, FileMode.Create);
    await file.CopyToAsync(stream);
    var post = new Post { Title = dto.Title, Content = dto.Content, ImageUrl = $"/uploads/{fileName}" };
    _context.Posts.Add(post);
    await _context.SaveChangesAsync();
    return Ok();
}
const handleSubmit = async (e) => {
  e.preventDefault();
  const data = new FormData();
  data.append("title", title);
  data.append("content", content);
  data.append("image", file);
  await fetch("/api/posts", {
    method: "POST",
    body: data
  });
};
🌐 4. SEO și rendering: SSR vs SPA
SPA (Single Page Application)
Avantaje:
Dezavantaje:
- 
Nu e ideal pentru crawlere (Googlebot etc.)
 - 
Fără prerendering, titlurile și meta tag-urile nu sunt vizibile
 
SSR (Server-Side Rendering) – opțional în viitor
Poți migra la:
✅ Concluzie
Aceste funcționalități transformă un blog simplu într-o aplicație completă și scalabilă:
✅ Comentarii și entități conectate
✅ Paginare și filtre pentru UX optim
✅ Upload imagini cover în mod elegant
✅ Gândire SEO pentru vizibilitate pe web